package ci
import (
"errors"
"log"
)
// TagStore is the record-keeping the tag walk needs beyond Store's: the
// tag watermark, the newest tag already considered, so a tag starts its
// workflows once (RFC 0020's `tag` event).
type TagStore interface {
Store
CITagWatermark(repo string) (TagMark, bool)
SetCITagWatermark(repo string, m TagMark) error
}
// TagRepo is the read side the tag walk needs beyond Repo's.
type TagRepo interface {
Repo
// TagsAfter lists the tags added to check-ins after a mark, oldest
// first; NewestTag is the newest one's mark, the zero mark when the
// repository has none.
TagsAfter(after TagMark, limit int) ([]Tagged, error)
NewestTag() (TagMark, error)
}
// TagMark names one T card that added a tag to a check-in, in the order
// the tag walk takes them: the carrying artifact's date, the artifact,
// the tagged check-in, the tag's name. The zero mark precedes every tag.
type TagMark struct {
Date string
Src string
Target string
Name string // bare: "version-0.2.0", never "sym-version-0.2.0"
}
// Tagged is a tag added to a check-in, and the check-in it was added
// to, branch included. Which T cards count is the index's rule
// (index.TagsAfter): a symbolic tag added, not a branch's own name.
type Tagged struct {
TagMark
At Checkin
}
// TriggerTags starts the workflows of tags added since the tag
// watermark (RFC 0020's `tag` event). A tag is usually added after its
// check-in, by a control artifact, so Trigger's check-in walk never
// sees it; the tag walk is its own, with the check-in walk's rules: a
// first sight starts nothing, a transient failure holds the watermark,
// a content fault advances it.
//
// A tag launch neither waits for a worker slot nor supersedes: a
// release is rare, deliberate, and not made stale by the next tag.
func TriggerTags(e *Engine, st TagStore, repo string, r TagRepo, cloneURL string) error {
if !e.Enabled() {
return nil
}
mark, ok := st.CITagWatermark(repo)
if !ok {
newest, err := r.NewestTag()
if err != nil {
return err
}
return st.SetCITagWatermark(repo, newest)
}
fresh, err := r.TagsAfter(mark, 20)
if err != nil {
return err
}
for _, t := range fresh {
c := t.At
c.Tag = t.Name
pipelines, err := pipelinesAt(r, c)
if err == nil {
err = launch(e, st, repo, r, c, cloneURL, pipelines, scopeTag)
}
if errors.Is(err, errTransient) {
log.Printf("ci: %s %.12s tag %s: %v — tag watermark held, retrying next round", repo, c.Hash, t.Name, err)
return nil
}
if err != nil {
log.Printf("ci: %s %.12s tag %s: %v", repo, c.Hash, t.Name, err)
}
if err := st.SetCITagWatermark(repo, t.TagMark); err != nil {
return err
}
}
return nil
}