diff --git a/api/docker/client/client.go b/api/docker/client/client.go index 065a40382..1161c4995 100644 --- a/api/docker/client/client.go +++ b/api/docker/client/client.go @@ -3,8 +3,8 @@ package client import ( "bytes" "errors" + "fmt" "io" - "maps" "net/http" "strings" "time" @@ -141,7 +141,6 @@ func createAgentClient(endpoint *portainer.Endpoint, endpointURL string, signatu type NodeNameTransport struct { *http.Transport - nodeNames map[string]string } func (t *NodeNameTransport) RoundTrip(req *http.Request) (*http.Response, error) { @@ -176,18 +175,19 @@ func (t *NodeNameTransport) RoundTrip(req *http.Request) (*http.Response, error) return resp, nil } - t.nodeNames = make(map[string]string) - for _, r := range rs { - t.nodeNames[r.ID] = r.Portainer.Agent.NodeName + nodeNames, ok := req.Context().Value("nodeNames").(map[string]string) + if ok { + for idx, r := range rs { + // as there is no way to differentiate the same image available in multiple nodes only by their ID + // we append the index of the image in the payload response to match the node name later + // from the image.Summary[] list returned by docker's client.ImageList() + nodeNames[fmt.Sprintf("%s-%d", r.ID, idx)] = r.Portainer.Agent.NodeName + } } return resp, err } -func (t *NodeNameTransport) NodeNames() map[string]string { - return maps.Clone(t.nodeNames) -} - func httpClient(endpoint *portainer.Endpoint, timeout *time.Duration) (*http.Client, error) { transport := &NodeNameTransport{ Transport: &http.Transport{}, diff --git a/api/http/handler/docker/images/images_list.go b/api/http/handler/docker/images/images_list.go index ac7e980a0..eca99d993 100644 --- a/api/http/handler/docker/images/images_list.go +++ b/api/http/handler/docker/images/images_list.go @@ -1,10 +1,11 @@ package images import ( + "context" + "fmt" "net/http" "strings" - "github.com/portainer/portainer/api/docker/client" "github.com/portainer/portainer/api/http/handler/docker/utils" "github.com/portainer/portainer/api/set" httperror "github.com/portainer/portainer/pkg/libhttp/error" @@ -46,17 +47,16 @@ func (handler *Handler) imagesList(w http.ResponseWriter, r *http.Request) *http return httpErr } - images, err := cli.ImageList(r.Context(), image.ListOptions{}) + nodeNames := make(map[string]string) + + // Pass the node names map to the context so the custom NodeNameTransport can use it + ctx := context.WithValue(r.Context(), "nodeNames", nodeNames) + + images, err := cli.ImageList(ctx, image.ListOptions{}) if err != nil { return httperror.InternalServerError("Unable to retrieve Docker images", err) } - // Extract the node name from the custom transport - nodeNames := make(map[string]string) - if t, ok := cli.HTTPClient().Transport.(*client.NodeNameTransport); ok { - nodeNames = t.NodeNames() - } - withUsage, err := request.RetrieveBooleanQueryParameter(r, "withUsage", true) if err != nil { return httperror.BadRequest("Invalid query parameter: withUsage", err) @@ -85,8 +85,12 @@ func (handler *Handler) imagesList(w http.ResponseWriter, r *http.Request) *http } imagesList[i] = ImageResponse{ - Created: image.Created, - NodeName: nodeNames[image.ID], + Created: image.Created, + // Only works if the order of `images` is not changed between unmarshaling the agent's response + // in NodeNameTransport.RoundTrip() (api/docker/client/client.go) + // and docker's cli.ImageList() + // As both functions unmarshal the same response body, the resulting array will be ordered the same way. + NodeName: nodeNames[fmt.Sprintf("%s-%d", image.ID, i)], ID: image.ID, Size: image.Size, Tags: image.RepoTags,