Followup changes to address todos in delete and list#14189
Followup changes to address todos in delete and list#14189yao-msft wants to merge 13 commits intofeature/wsl-for-appsfrom
Conversation
…yaosun/todolistdelete
There was a problem hiding this comment.
Pull request overview
Updates the WSLA image list/delete surface area by expanding ListImages to accept filter options and returning additional image metadata, while shifting DeleteImage to a flags-based options model; corresponding Docker API plumbing and Windows tests are updated to match.
Changes:
- Extend
IWSLASession::ListImageswith options (filters/flags) andWSLA_IMAGE_INFORMATIONwith digest/created/parent metadata. - Replace
WSLA_DELETE_IMAGE_OPTIONSboolean fields with aFlagsbitfield and updateDeleteImagehandling. - Add/expand WSLA tests to cover list filtering and new image metadata fields.
Reviewed changes
Copilot reviewed 7 out of 7 changed files in this pull request and generated 8 comments.
Show a summary per file
| File | Description |
|---|---|
| test/windows/WSLATests.cpp | Updates ListImages/DeleteImage usage and adds broader ListImages test coverage (filters/flags/fields). |
| src/windows/wslaservice/inc/wslaservice.idl | Updates COM IDL types and ListImages signature; introduces new flags/options structs. |
| src/windows/wslaservice/exe/WSLASession.h | Updates WSLASession::ListImages method signature to match IDL. |
| src/windows/wslaservice/exe/WSLASession.cpp | Implements ListImages options/filters, populates new fields, and updates DeleteImage to use flags. |
| src/windows/wslaservice/exe/DockerHTTPClient.h | Adds ListImages filter struct and expands ListImages API to accept flags/filters. |
| src/windows/wslaservice/exe/DockerHTTPClient.cpp | Builds Docker /images/json query parameters + JSON filters for ListImages. |
| src/windows/inc/docker_schema.h | Extends Docker image schema parsing to include digests/created/parent id. |
|
|
||
| HRESULT WSLASession::ListImages(const WSLA_LIST_IMAGES_OPTIONS* Options, WSLA_IMAGE_INFORMATION** Images, ULONG* Count, WSLA_ERROR_INFO* Error) | ||
| try | ||
| { |
There was a problem hiding this comment.
ListImages dereferences Count and Images immediately (*Count = 0; *Images = nullptr;) but does not validate they are non-null. Other COM methods in this file (e.g. DeleteImage) return E_POINTER for null out-params; ListImages should do the same to avoid potential AVs in the service when called incorrectly.
| { | |
| { | |
| if (Images == nullptr || Count == nullptr) | |
| { | |
| return E_POINTER; | |
| } |
| if ((e.StatusCode() >= 400 && e.StatusCode() < 500)) | ||
| { | ||
| errorMessage = e.DockerMessage<docker_schema::ErrorResponse>().message; | ||
| if (Error != nullptr) | ||
| { | ||
| Error->UserErrorMessage = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(errorMessage.c_str()).release(); | ||
| } | ||
| } | ||
|
|
There was a problem hiding this comment.
In the DockerHTTPException handler, errorMessage is only populated for 4xx responses; for 5xx/other failures this throws E_FAIL with an empty message and leaves ErrorInfo unset. Populate a meaningful message for non-4xx cases (e.g., from the response body / exception details) so callers get actionable diagnostics.
| if ((e.StatusCode() >= 400 && e.StatusCode() < 500)) | |
| { | |
| errorMessage = e.DockerMessage<docker_schema::ErrorResponse>().message; | |
| if (Error != nullptr) | |
| { | |
| Error->UserErrorMessage = wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(errorMessage.c_str()).release(); | |
| } | |
| } | |
| // First, try to extract a Docker error response message, regardless of status code. | |
| try | |
| { | |
| const auto dockerError = e.DockerMessage<docker_schema::ErrorResponse>(); | |
| if (!dockerError.message.empty()) | |
| { | |
| errorMessage = dockerError.message; | |
| } | |
| } | |
| catch (...) | |
| { | |
| // If we cannot parse a Docker error message, fall back to a generic one below. | |
| } | |
| // If we still do not have a message, construct a generic one with the status code. | |
| if (errorMessage.empty()) | |
| { | |
| errorMessage = wil::str_printf<std::string>( | |
| "Docker API request failed with status code %u.", e.StatusCode()); | |
| } | |
| if (Error != nullptr && !errorMessage.empty()) | |
| { | |
| Error->UserErrorMessage = | |
| wil::make_unique_ansistring<wil::unique_cotaskmem_ansistring>(errorMessage.c_str()).release(); | |
| } |
| // Extract repo name from tag (format: "repo:tag") | ||
| // and lookup corresponding digest from the map | ||
| size_t colonPos = tag.find(':'); | ||
| if (colonPos != std::string::npos && colonPos > 0) | ||
| { | ||
| std::string repoName = tag.substr(0, colonPos); | ||
| auto it = repoToDigest.find(repoName); | ||
| if (it != repoToDigest.end()) | ||
| { | ||
| THROW_HR_IF(E_UNEXPECTED, strcpy_s(output[index].Digest, it->second.c_str()) != 0); | ||
| } | ||
| else | ||
| { | ||
| output[index].Digest[0] = '\0'; | ||
| } | ||
| } | ||
| else | ||
| { | ||
| output[index].Digest[0] = '\0'; | ||
| } |
There was a problem hiding this comment.
Digest lookup extracts the repo name from a tag by using tag.find(':'), which breaks for image references containing a registry port (e.g. localhost:5000/repo:tag): the repo becomes localhost and won’t match RepoDigests entries. Parse the tag using the last : that appears after the last / (Docker tag separator rules) before looking up the digest.
| options.Before = nullptr; | ||
| options.Since = debianId.c_str(); | ||
| options.Labels = nullptr; | ||
| options.LabelsCount = 0; | ||
|
|
There was a problem hiding this comment.
Indentation looks off for options.LabelsCount = 0; in this block, which hurts readability and makes it easy to miss the assignment. Please align it with the surrounding options.* assignments.
| options.Reference = nullptr; | ||
| options.Before = alpineId.c_str(); | ||
| options.Since = nullptr; | ||
| options.Labels = nullptr; | ||
| options.LabelsCount = 0; | ||
|
|
There was a problem hiding this comment.
Indentation looks off for options.LabelsCount = 0; in this block as well. Please reformat so the assignment is aligned with the other options.* lines.
| HRESULT ImportImage([in] ULONG ImageHandle, [in] LPCSTR ImageName, [in, unique] IProgressCallback* ProgressCallback, [in] ULONGLONG ContentLength); | ||
| HRESULT SaveImage([in] ULONG OutputHandle, [in] LPCSTR ImageNameOrID, [in, unique] IProgressCallback * ProgressCallback, [in, out, unique] WSLA_ERROR_INFO * ErrorInfo); | ||
| HRESULT ListImages([out, size_is(, *Count)] struct WSLA_IMAGE_INFORMATION** Images, [out] ULONG* Count); | ||
| HRESULT ListImages([in, unique] const struct WSLA_LIST_IMAGES_OPTIONS* Options, [out, size_is(, *Count)] struct WSLA_IMAGE_INFORMATION** Images, [out] ULONG* Count, [in, out, unique] WSLA_ERROR_INFO* ErrorInfo); |
There was a problem hiding this comment.
Changing the signature of an existing COM interface method (IWSLASession::ListImages) without changing the interface IID breaks COM binary compatibility: older clients can end up calling the wrong vtable slot/with the wrong arguments. Consider introducing a new interface (e.g. IWSLASession2 with a new UUID) or adding a new method while keeping the old ListImages contract intact.
| HRESULT ListImages([in, unique] const struct WSLA_LIST_IMAGES_OPTIONS* Options, [out, size_is(, *Count)] struct WSLA_IMAGE_INFORMATION** Images, [out] ULONG* Count, [in, out, unique] WSLA_ERROR_INFO* ErrorInfo); | |
| HRESULT ListImages([in, unique] const struct WSLA_LIST_IMAGES_OPTIONS* Options, [out, size_is(, *Count)] struct WSLA_IMAGE_INFORMATION** Images, [out] ULONG* Count); | |
| HRESULT ListImages2([in, unique] const struct WSLA_LIST_IMAGES_OPTIONS* Options, [out, size_is(, *Count)] struct WSLA_IMAGE_INFORMATION** Images, [out] ULONG* Count, [in, out, unique] WSLA_ERROR_INFO* ErrorInfo); |
| char Image[WSLA_MAX_IMAGE_NAME_LENGTH + 1]; | ||
| char Hash[256]; | ||
| char Digest[256]; | ||
| ULONGLONG Size; |
There was a problem hiding this comment.
WSLA_IMAGE_INFORMATION layout changed (new fields, removed DownloadTimestamp). For COM marshaling/binary consumers, changing an existing struct used in an interface method is a breaking ABI change. If compatibility matters, consider defining a new struct (e.g. WSLA_IMAGE_INFORMATION_V2) and returning that from a new method/interface.
| ULONGLONG Size; | |
| ULONGLONG Size; | |
| LONGLONG DownloadTimestamp; // Unix timestamp when the image was downloaded (retained for ABI compatibility) |
| struct WSLA_DELETE_IMAGE_OPTIONS | ||
| { | ||
| LPCSTR Image; // Image can be ID or Repo:Tag. | ||
| BOOL Force; | ||
| BOOL NoPrune; | ||
| DWORD Flags; // WSLADeleteImageFlags | ||
| // TODO: Platforms: a json array of OCI platform strings. |
There was a problem hiding this comment.
WSLA_DELETE_IMAGE_OPTIONS changed from two BOOL fields to a Flags DWORD. As with other COM structs, changing the layout of an existing options struct is a breaking change for any client compiled against the old IDL. If backward compatibility is required, keep the old struct and add a new *_V2 struct or new method.
| WSLAListImagesFlagsNone = 0, | ||
| WSLAListImagesFlagsAll = 1, // Show all images (default hides intermediate images) | ||
| WSLAListImagesFlagsDigests = 2, // Include digest information | ||
| WSLAListImagesFlagsDanglingTrue = 4, // Show only dangling images (untagged) |
There was a problem hiding this comment.
nit: We could simplify this a bit by having:
WSLAListImagesFlagsShowDangling,
WSLAListImagesFlagsShowNonDangling,
And having the caller set those by default, that way we don't have to do any special logic for it in the service
There was a problem hiding this comment.
Is this comment just about renaming the flag?
Summary of the Pull Request
Followup changes to address todos in delete and list
PR Checklist
Detailed Description of the Pull Request / Additional comments
Validation Steps Performed
Added more tests.