Files
tiny-forge/internal/registry/registry.go
T
alexei.dolgolyov 90be636d66 feat(docker-watcher): phase 5 - registry client & poller
Gitea registry client with tag listing and pattern matching, cron-based
polling scheduler with first-poll safety, poll state persistence.
DeployTriggerer interface for decoupled deploy triggering.
2026-03-27 21:34:09 +03:00

79 lines
2.4 KiB
Go

package registry
import (
"context"
"fmt"
"path"
"sort"
"strings"
)
// 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)
}
// 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)
}
}