wslc: add --digests to image list for docker parity - #41457
wslc: add --digests to image list for docker parity#41457ggarzia-MSFT wants to merge 12 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This pull request adds --digests support to wslc image list to match Docker parity, wiring the flag through CLI argument parsing into the image listing service and updating both table and JSON render paths to only surface digests when explicitly requested.
Changes:
- Added a new
--digestsflag (no short alias) forimage list/image ls/images, and threaded it throughImageService::List. - Implemented digest formatting by reducing service “repo@sha256:…” values to Docker’s “sha256:…” form via
DigestFromRepoDigest, and conditionally added a DIGEST column in table output. - Added unit + E2E tests to lock the gating behavior (digests only appear when
--digestsis passed) and validate output parity between JSON and table formats.
Reviewed changes
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Show a summary per file
| File | Description |
|---|---|
| test/windows/wslc/WSLCCLIImageDigestUnitTests.cpp | New unit tests for DigestFromRepoDigest behavior. |
| test/windows/wslc/WSLCCLICommandUnitTests.cpp | Verifies image list/images register --digests with no short alias. |
| test/windows/wslc/e2e/WSLCE2EImageListTests.cpp | Adds E2E coverage for DIGEST column placement, gating, help output, and quiet behavior. |
| src/windows/wslc/tasks/ImageTasks.cpp | Threads --digests into listing and conditionally adds DIGEST column + JSON field population. |
| src/windows/wslc/services/ImageService.h | Extends ImageService::List signature with a digests parameter (defaulted). |
| src/windows/wslc/services/ImageService.cpp | Sets the WSLC digest flag and populates ImageInformation::Digest only when requested. |
| src/windows/wslc/services/ImageModel.h | Adds Digest field to the image model and introduces DigestFromRepoDigest. |
| src/windows/wslc/commands/ImageListCommand.cpp | Registers the new ArgType::Digests argument for the list command. |
| src/windows/wslc/arguments/ArgumentDefinitions.h | Defines the Digests argument metadata and localization key mapping. |
| localization/strings/en-US/Resources.resw | Adds localized description string for --digests. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 10 out of 10 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
localization/strings/en-US/Resources.resw:3023
- Most CLI argument descriptions in Resources.resw do not end with a period; this new string does, which makes help output inconsistent.
<value>Show image digests.</value>
David Bennett (dkbennett)
left a comment
There was a problem hiding this comment.
--quiet and --digests are valid options together and Docker CLI allows both. --quiet means only image ids are output, but --digests means that an image with multiple digests can appear multiple times in the output (same is true for tags)
Should add tests here which verify the --quiet and --digests combination (and also images with multiple tags should appear multiple times with --quiet).
This may be a pre-existing issue with quiet that could be addressed here.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…olumn Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
localization/strings/en-US/Resources.resw:3024
- Most CLI argument descriptions in Resources.resw omit trailing punctuation (e.g., "Run container in detached mode"). For consistency, drop the trailing period from this new description.
<data name="WSLCCLI_DigestsArgDescription" xml:space="preserve">
<value>Show image digests.</value>
</data>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
test/windows/wslc/e2e/WSLCE2EImageListTests.cpp:495
- This assertion is logically correct, but it reads as a double-negative ("IS_FALSE(find != npos)"). Using an explicit npos comparison makes failures easier to interpret and avoids precedence/clarity pitfalls in future edits.
VERIFY_IS_FALSE(defaultResult.GetStdoutLines()[0].find(L"DIGEST") != std::wstring::npos);
src/windows/wslcsession/WSLCSession.cpp:1958
- When WSLCListImagesFlagsDigests is not set, this branch still emits a digest value (it->second.front()) if RepoDigests are present. Since the flag is meant to gate whether digest info is included at all, it would be safer to always emit an empty digest when digests==false (even if the backend unexpectedly provides RepoDigests).
else if (!digests)
{
rows.push_back({&e, tag, it->second.front()});
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (3)
Previously missed (2) — in code that hasn't changed since the last review.
test/windows/wslc/WSLCCLITableOutputUnitTests.cpp:274
- In this test, namePos/statusPos are used for arithmetic without first verifying the substrings were found. If either find() returns npos, the expected-position math can overflow and the assertion may become misleading.
const auto namePos = dataLine.find(L"container-a");
const auto statusPos = dataLine.find(L"running");
const auto expected = namePos + wcslen(L"container-a") + TableOutput<3>::DefaultColumnPadding + wcslen(L"DIGEST") +
TableOutput<3>::DefaultColumnPadding;
VERIFY_ARE_EQUAL(expected, statusPos);
test/windows/wslc/WSLCCLITableOutputUnitTests.cpp:306
- This assertion uses namePos in arithmetic without checking that "container-a" (and "running") were actually found. If either find() returns npos, the computed offset can overflow and hide formatting regressions.
const auto& dataLine = cap.lines()[1];
const auto namePos = dataLine.find(L"container-a");
VERIFY_ARE_EQUAL(namePos + wcslen(L"container-a") + TableOutput<3>::DefaultColumnPadding, dataLine.find(L"running"));
localization/strings/en-US/Resources.resw:3024
- This new argument description includes a trailing period, which is inconsistent with nearby argument descriptions (e.g., "Run container in detached mode") and will show up in --help output.
<data name="WSLCCLI_DigestsArgDescription" xml:space="preserve">
<value>Show image digests.</value>
</data>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 13 out of 13 changed files in this pull request and generated 1 comment.
Suppressed comments (1)
test/windows/wslc/e2e/WSLCE2EImageListTests.cpp:549
- This assertion only checks whether the digest text appears anywhere in the table output. That can pass even if the value appears in a different column (or in multiple places), so it doesn't actually verify the DIGEST column as the comment claims. Consider extracting the DIGEST column slice using the header offsets and comparing against that field.
// Every digest reported by json output must appear in the table's DIGEST column, so the two
// renderings cannot drift.
for (const auto& image : ParseNdjsonOutputAs<ImageOutputInformation>(jsonResult))
{
const auto digest = wsl::shared::string::MultiByteToWide(image.Digest);
VERIFY_IS_TRUE(
tableResult.StdoutContainsSubstring(digest),
WEX::Common::String().Format(L"'%ls' was missing from the table DIGEST column", digest.c_str()));
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
src/windows/wslcsession/WSLCSession.cpp:1981
- This branch also adds a digest value when
digestsis false (repoDigests.front()). To keepWSLCListImagesFlagsDigestssemantics consistent, avoid populating the digest field unlessdigestsis true (you can still keep the row if you intend to preserve repository-only-by-digest grouping).
if (!digests)
{
rows.push_back({&e, repoName, repoDigests.front()});
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
src/windows/wslc/services/ImageModel.h:57
DigestFromRepoDigestreturns astd::string_viewthat aliases the input buffer. This is easy to misuse (e.g., passing a temporarystd::stringand storing the returned view past the full-expression), which can lead to dangling references/UB. Since this helper is in a header and can be reused elsewhere, it would be safer for it to return an owningstd::string(or otherwise guarantee lifetime) rather than a view.
inline std::string_view DigestFromRepoDigest(std::string_view repoDigest)
{
const auto separator = repoDigest.find('@');
return separator == std::string_view::npos ? repoDigest : repoDigest.substr(separator + 1);
}
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 16 out of 16 changed files in this pull request and generated no new comments.
Suppressed comments (2)
Previously missed (1) — in code that hasn't changed since the last review.
test/windows/wslc/WSLCCLICommandUnitTests.cpp:156
- This array initializer is very long and likely exceeds the 130-column limit enforced by clang-format (.clang-format ColumnLimit: 130). Splitting the entries across lines will avoid formatting-check failures and improve readability.
const std::pair<std::wstring, std::vector<Argument>> spellings[] = {
{L"image list", ImageListCommand(L"image").GetArguments()}, {L"images", ImageListCommand(L"wslc", true).GetArguments()}};
src/windows/wslc/tasks/ImageTasks.cpp:241
- This braced initializer for the table column definitions is likely over the repo’s 130-column limit (see .clang-format ColumnLimit: 130), which can cause format verification to fail and hurts readability. Please wrap each column definition onto its own line.
auto table =
trunc
? wsl::windows::wslc::TableOutput<6>(
context.Terminal,
{{{L"REPOSITORY", c_shrink}, {L"TAG", c_shrink}, {L"DIGEST", c_shrink}, {L"IMAGE ID", c_imageId}, {L"CREATED", c_shrink}, {L"SIZE", c_shrink}}},
images.size())
Summary of the Pull Request
Adds
--digeststowslc image list(and itsimage ls/imagesspellings), matchingdocker images --digests. The DIGEST column was previously hardcoded to<none>in json output and absent from the table.PR Checklist
Detailed Description of the Pull Request / Additional comments
WSLCListImagesFlagsDigestswas already defined and already honored end to end —WSLCSession::ListImagesforwards it toDocker().ListImages(all, digests, filters)and populatesWSLCImageInformation::Digest. The CLI never set the flag and never read the field, soToImageOutputhardcodedDigestto<none>.ArgType::Digests(--digests, no short alias, matching docker where only listing commands'--filtercarries-f).models::ImageInformationgains aDigestfield, andImageService::Listtakes a defaultedbool digeststhat sets the flag and populates it.--digestsis passed, matching docker'sdefaultImageTableFormatWithDigest.repo@sha256:...); docker's DIGEST column shows only the digest. NewDigestFromRepoDigesthelper inImageModel.hperforms that reduction and is unit tested directly.needDigest(ctx), which isctx.Digest || format.Contains("{{.Digest}}"), and--format jsonsatisfies neither. Soimage list --format jsonstill reports<none>, and only--digests --format jsonreports a real digest. An earlier revision of this change leaked the digest into plain json output; the tests below lock the gated behavior in.constexprvalues so the 5- and 6-column tables cannot drift.Validation Steps Performed
All new and existing tests in the touched areas were run against a locally deployed build. 25/25 pass.
Unit tests (
WSLCCLIImageDigestUnitTests, new file, 3 tests):DigestFromRepoDigest_StripsRepositoryPrefix— plain, fully qualified, and registry-with-port repositories all reduce to the baresha256:digest. The port case guards the split from being confused by a colon.DigestFromRepoDigest_PassesThroughBareValues— empty, already-bare, and<none>values are untouched.DigestFromRepoDigest_SplitsOnFirstSeparator— splitting happens once, so a digest is never truncated further.Unit test (
WSLCCLICommandUnitTests):ImageListCommand_HasDigestsArgument— both theimage listand root-scopedimagesconstructions register--digestsas an optional flag with no short alias.E2E tests (
WSLCE2EImageListTests, 22 total, 5 new):Digests_AddsColumnBetweenTagAndImageId— asserts DIGEST sits after TAG and before IMAGE ID, and that the default listing does not gain the column.Digests_ReportsDigestOnlyWhenRequested— json reports<none>without the flag; anything reported with the flag must be a baresha256:digest with norepo@prefix.Digests_TableMatchesJson— every digest in json output appears in the table's DIGEST column, so the two renderings cannot drift.Digests_QuietStillOutputsIdsOnly—--quietwins over--digests, emitting bare ids with no header, as docker does.Digests_ListedInHelp—--digestsand its localized description appear inimage list --help.Note on the test images: they are provisioned via
image loadfrom a tarball, so they carry no repo digest and correctly report<none>even with--digests— the same as docker for a tar-loaded image. The digest-reduction logic is therefore covered by the unit tests rather than by asserting a live registry digest in e2e, which would make the suite network dependent.