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.
This commit is contained in:
@@ -0,0 +1,78 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user