37e251da85
- 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
90 lines
2.9 KiB
Go
90 lines
2.9 KiB
Go
package registry
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
"sort"
|
|
"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.
|
|
ListTags(ctx context.Context, image string) ([]string, error)
|
|
|
|
// 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
|
|
// stage with auto_deploy enabled. This decouples the registry package from the
|
|
// deployer implementation.
|
|
type DeployTriggerer interface {
|
|
TriggerDeploy(ctx context.Context, projectID, stageID, imageTag string) error
|
|
}
|
|
|
|
// MatchTags filters a list of tags, returning only those that match the given
|
|
// glob pattern. Pattern matching uses path.Match semantics (*, ?, []).
|
|
// Returns an error if the pattern is malformed.
|
|
func MatchTags(tags []string, pattern string) ([]string, error) {
|
|
if pattern == "" || pattern == "*" {
|
|
result := make([]string, len(tags))
|
|
copy(result, tags)
|
|
return result, nil
|
|
}
|
|
|
|
// Validate pattern once before iterating.
|
|
if _, err := path.Match(pattern, ""); err != nil {
|
|
return nil, fmt.Errorf("invalid tag pattern %q: %w", pattern, err)
|
|
}
|
|
|
|
var matched []string
|
|
for _, tag := range tags {
|
|
ok, _ := path.Match(pattern, tag)
|
|
if ok {
|
|
matched = append(matched, tag)
|
|
}
|
|
}
|
|
return matched, nil
|
|
}
|
|
|
|
// LatestTag returns the last element of a sorted tag list that matches the
|
|
// pattern. Tags are sorted lexicographically; the "latest" is the last in sort
|
|
// order. Returns empty string if no tags match. Returns an error if the pattern
|
|
// is malformed.
|
|
func LatestTag(tags []string, pattern string) (string, error) {
|
|
matched, err := MatchTags(tags, pattern)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(matched) == 0 {
|
|
return "", nil
|
|
}
|
|
sort.Strings(matched)
|
|
return matched[len(matched)-1], nil
|
|
}
|
|
|
|
// NewClient creates a registry Client based on the registry type string.
|
|
// Supported types: "gitea". Future: "github", "dockerhub".
|
|
func NewClient(registryType, baseURL, token string) (Client, error) {
|
|
switch strings.ToLower(registryType) {
|
|
case "gitea":
|
|
return NewGiteaClient(baseURL, token), nil
|
|
default:
|
|
return nil, fmt.Errorf("unsupported registry type: %s", registryType)
|
|
}
|
|
}
|