feat: auto-discover container images from registries
- Add ListImages() to registry interface, implement for Gitea - Add owner field to registry config (needed for Gitea packages API) - GET /api/registries/:id/images endpoint - "Browse Images" button on Projects and Quick Deploy pages - Image dropdown with registry grouping and search - i18n support (EN/RU) for all new UI strings
This commit is contained in:
@@ -41,6 +41,80 @@ func NewGiteaClient(baseURL, token string) *GiteaClient {
|
||||
}
|
||||
}
|
||||
|
||||
// ListImages returns all container images (packages) for the given owner.
|
||||
// It queries GET /api/v1/packages/{owner}?type=container and paginates
|
||||
// through all results, returning a RegistryImage for each unique package.
|
||||
func (c *GiteaClient) ListImages(ctx context.Context, owner string) ([]RegistryImage, error) {
|
||||
if owner == "" {
|
||||
return nil, fmt.Errorf("owner is required for listing images")
|
||||
}
|
||||
|
||||
// Extract the registry host from baseURL to build full references.
|
||||
host := c.baseURL
|
||||
for _, prefix := range []string{"https://", "http://"} {
|
||||
host = strings.TrimPrefix(host, prefix)
|
||||
}
|
||||
host = strings.TrimRight(host, "/")
|
||||
|
||||
var images []RegistryImage
|
||||
seen := make(map[string]bool)
|
||||
page := 1
|
||||
limit := 50
|
||||
|
||||
for {
|
||||
url := fmt.Sprintf("%s/api/v1/packages/%s?type=container&page=%d&limit=%d",
|
||||
c.baseURL, owner, page, limit)
|
||||
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("create request: %w", err)
|
||||
}
|
||||
|
||||
if c.token != "" {
|
||||
req.Header.Set("Authorization", "token "+c.token)
|
||||
}
|
||||
req.Header.Set("Accept", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("execute request: %w", err)
|
||||
}
|
||||
|
||||
body, err := io.ReadAll(resp.Body)
|
||||
resp.Body.Close()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("read response body: %w", err)
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
return nil, fmt.Errorf("unexpected status %d: %s", resp.StatusCode, string(body))
|
||||
}
|
||||
|
||||
var packages []giteaPackageListEntry
|
||||
if err := json.Unmarshal(body, &packages); err != nil {
|
||||
return nil, fmt.Errorf("decode package list: %w", err)
|
||||
}
|
||||
|
||||
for _, p := range packages {
|
||||
if !seen[p.Name] {
|
||||
seen[p.Name] = true
|
||||
images = append(images, RegistryImage{
|
||||
Name: p.Name,
|
||||
Owner: owner,
|
||||
FullRef: fmt.Sprintf("%s/%s/%s", host, owner, p.Name),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if len(packages) < limit {
|
||||
break
|
||||
}
|
||||
page++
|
||||
}
|
||||
|
||||
return images, nil
|
||||
}
|
||||
|
||||
// ListTags returns all available tags for the given container image.
|
||||
// The image should be in the format "owner/package-name" or
|
||||
// "registry-host/owner/package-name" (the registry host prefix is stripped).
|
||||
|
||||
@@ -8,6 +8,13 @@ import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
// RegistryImage represents a container image discovered from a registry.
|
||||
type RegistryImage struct {
|
||||
Name string `json:"name"`
|
||||
Owner string `json:"owner"`
|
||||
FullRef string `json:"full_ref"` // e.g., "git.example.com/owner/my-app"
|
||||
}
|
||||
|
||||
// Client defines the interface for interacting with a container image registry.
|
||||
type Client interface {
|
||||
// ListTags returns all available tags for the given image.
|
||||
@@ -16,6 +23,10 @@ type Client interface {
|
||||
// GetLatestTag returns the most recently created tag that matches the given
|
||||
// glob pattern. Returns an empty string and no error if no tags match.
|
||||
GetLatestTag(ctx context.Context, image string, pattern string) (string, error)
|
||||
|
||||
// ListImages returns all container images available in the registry for the
|
||||
// given owner. Returns an error if the registry does not support image listing.
|
||||
ListImages(ctx context.Context, owner string) ([]RegistryImage, error)
|
||||
}
|
||||
|
||||
// DeployTriggerer is called by the poller when a new tag is detected for a
|
||||
|
||||
Reference in New Issue
Block a user