lazydocker/pkg/commands/image_registry.go

96 lines
3.1 KiB
Go

package commands
import (
"context"
"encoding/json"
"fmt"
"net/http"
"net/url"
"strings"
"time"
)
const dockerHubAPIBaseURL = "https://hub.docker.com"
var dockerHubHTTPClient = &http.Client{Timeout: 15 * time.Second}
// DockerHubTag contains the metadata Docker Hub returns for a repository tag.
type DockerHubTag struct {
Name string `json:"name"`
FullSize int64 `json:"full_size"`
LastUpdated string `json:"last_updated"`
TagLastPushed string `json:"tag_last_pushed"`
Digest string `json:"digest"`
MediaType string `json:"media_type"`
Images []DockerHubTagVariant `json:"images"`
}
// DockerHubTagVariant describes one platform-specific image behind a tag.
type DockerHubTagVariant struct {
Architecture string `json:"architecture"`
Variant string `json:"variant"`
Digest string `json:"digest"`
OS string `json:"os"`
Size int64 `json:"size"`
LastPushed string `json:"last_pushed"`
}
// SearchImageTags searches Docker Hub tags for an image repository.
func (c *DockerCommand) SearchImageTags(ctx context.Context, imageName, query string) ([]DockerHubTag, error) {
return searchImageTags(ctx, dockerHubHTTPClient, dockerHubAPIBaseURL, imageName, query)
}
func searchImageTags(ctx context.Context, httpClient *http.Client, baseURL, imageName, query string) ([]DockerHubTag, error) {
namespace, repository, err := dockerHubRepositoryParts(imageName)
if err != nil {
return nil, err
}
endpoint, err := url.Parse(strings.TrimRight(baseURL, "/"))
if err != nil {
return nil, fmt.Errorf("build Docker Hub URL: %w", err)
}
endpoint.Path = fmt.Sprintf("/v2/namespaces/%s/repositories/%s/tags", url.PathEscape(namespace), url.PathEscape(repository))
values := endpoint.Query()
values.Set("page_size", "50")
if query = strings.TrimSpace(query); query != "" {
values.Set("name", query)
}
endpoint.RawQuery = values.Encode()
request, err := http.NewRequestWithContext(ctx, http.MethodGet, endpoint.String(), nil)
if err != nil {
return nil, fmt.Errorf("build Docker Hub tag request: %w", err)
}
request.Header.Set("Accept", "application/json")
response, err := httpClient.Do(request)
if err != nil {
return nil, fmt.Errorf("search Docker Hub tags: %w", err)
}
defer response.Body.Close()
if response.StatusCode != http.StatusOK {
return nil, fmt.Errorf("search Docker Hub tags: %s", response.Status)
}
var payload struct {
Results []DockerHubTag `json:"results"`
}
if err := json.NewDecoder(response.Body).Decode(&payload); err != nil {
return nil, fmt.Errorf("parse Docker Hub tags: %w", err)
}
return payload.Results, nil
}
func dockerHubRepositoryParts(imageName string) (string, string, error) {
parts := strings.SplitN(strings.TrimSpace(imageName), "/", 2)
if len(parts) == 1 && parts[0] != "" {
return "library", parts[0], nil
}
if len(parts) == 2 && parts[0] != "" && parts[1] != "" {
return parts[0], parts[1], nil
}
return "", "", fmt.Errorf("invalid Docker Hub image name %q", imageName)
}