90be636d66
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.
91 lines
2.8 KiB
Go
91 lines
2.8 KiB
Go
package webhook
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"path"
|
|
|
|
"github.com/alexei/docker-watcher/internal/store"
|
|
)
|
|
|
|
// FindProjectAndStage searches for a project whose image matches the parsed
|
|
// image reference, then finds the stage whose tag pattern matches the incoming
|
|
// tag. Returns (project, stage, found, error).
|
|
//
|
|
// Matching logic:
|
|
// 1. Iterate all projects.
|
|
// 2. Compare the project's Image field against the parsed image's FullName().
|
|
// 3. For the matched project, iterate its stages and find one whose TagPattern
|
|
// matches the incoming tag using path.Match (glob semantics).
|
|
// 4. If multiple stages match, the first match wins (stages are ordered by name).
|
|
func FindProjectAndStage(ctx context.Context, st *store.Store, parsed ParsedImage) (store.Project, store.Stage, bool, error) {
|
|
projects, err := st.GetAllProjects()
|
|
if err != nil {
|
|
return store.Project{}, store.Stage{}, false, fmt.Errorf("get projects: %w", err)
|
|
}
|
|
|
|
imageName := parsed.FullName()
|
|
|
|
for _, project := range projects {
|
|
if !imageMatches(project.Image, imageName) {
|
|
continue
|
|
}
|
|
|
|
stage, found, err := matchStage(st, project.ID, parsed.Tag)
|
|
if err != nil {
|
|
return store.Project{}, store.Stage{}, false, fmt.Errorf("match stage for project %s: %w", project.Name, err)
|
|
}
|
|
if found {
|
|
return project, stage, true, nil
|
|
}
|
|
|
|
// Project matches but no stage pattern matches this tag.
|
|
// Return project with empty stage — caller can decide what to do.
|
|
// For now, we treat it as "not found" so auto-create doesn't fire
|
|
// for known projects with no matching stage.
|
|
return store.Project{}, store.Stage{}, false, nil
|
|
}
|
|
|
|
return store.Project{}, store.Stage{}, false, nil
|
|
}
|
|
|
|
// imageMatches checks if a project's stored image name matches the parsed
|
|
// image name. The comparison is case-sensitive and supports the project image
|
|
// being stored as either "owner/name" or just "name".
|
|
func imageMatches(projectImage, incomingImage string) bool {
|
|
if projectImage == incomingImage {
|
|
return true
|
|
}
|
|
// Also match if the incoming image has an owner prefix but the project
|
|
// only stores the bare name (or vice versa). This handles registries
|
|
// that include or omit the owner segment.
|
|
return false
|
|
}
|
|
|
|
// matchStage finds the first stage of a project whose tag pattern matches the
|
|
// given tag. Uses path.Match for glob-style matching (same as the registry poller).
|
|
func matchStage(st *store.Store, projectID, tag string) (store.Stage, bool, error) {
|
|
stages, err := st.GetStagesByProjectID(projectID)
|
|
if err != nil {
|
|
return store.Stage{}, false, fmt.Errorf("get stages: %w", err)
|
|
}
|
|
|
|
for _, stage := range stages {
|
|
pattern := stage.TagPattern
|
|
if pattern == "" {
|
|
pattern = "*"
|
|
}
|
|
|
|
matched, err := path.Match(pattern, tag)
|
|
if err != nil {
|
|
// Invalid pattern — skip this stage.
|
|
continue
|
|
}
|
|
if matched {
|
|
return stage, true, nil
|
|
}
|
|
}
|
|
|
|
return store.Stage{}, false, nil
|
|
}
|