Documentation
¶
Overview ¶
Package version parses/compares semver, evaluates constraints (keyword bumps + Terraform ranges), and runs the candidate-selection filter chain. Pure; no I/O.
Index ¶
- func Compare(a, b *Version) int
- func IsVariant(segment string) bool
- func Qualifier(tag string) string
- func Select[T any](current *Version, cands []T, attrs func(T) Attrs, opts ...Option) (T, bool)
- func SplitVariant(tag string) (string, string)
- func TooFresh(now, published time.Time, cooldown time.Duration) bool
- type Attrs
- type Bound
- type Constraint
- type Option
- func WithAsset(preds ...Predicate) Option
- func WithBareMajor(allow bool) Option
- func WithBehind(n int) Option
- func WithConstraint(c *Constraint) Option
- func WithCooldown(d time.Duration) Option
- func WithDowngrade(allow bool) Option
- func WithExact(target string) Option
- func WithExclude(preds ...Predicate) Option
- func WithInclude(preds ...Predicate) Option
- func WithNow(t time.Time) Option
- func WithObserver(fn func(tag string, reason Reason)) Option
- func WithPrerelease(allow bool) Option
- func WithQualifier(q string) Option
- type Predicate
- type Reason
- type Version
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
func Compare ¶
Compare orders a and b, returning -1, 0, or +1. The numeric segments and the semver rule that a release outranks any prerelease are honored as usual, but prerelease identifiers are compared with natural (digit-aware) ordering rather than lexically. That keeps the conventional alpha < beta < rc progression while also ranking multi-digit suffixes correctly (beta9 < beta10), which a lexical compare reverses.
func IsVariant ¶
IsVariant reports whether a trailing dash-segment is a recognized image variant (a suffix to preserve) rather than a prerelease. It matches the first dash-delimited word with trailing version digits stripped, so alpine3.19 and slim-bookworm both register.
func Qualifier ¶
Qualifier returns the trailing dash-suffix of a tag - the prerelease or variant portion, recognized or not: "1.15.0-ent" -> "ent", "1.27-slim-bookworm" -> "slim-bookworm", "v3.5.0" -> "", "3.18.0" -> "". Build metadata (+...) is dropped. Unlike SplitVariant, which only reports a curated set, this reports whatever decorates the version, so a marker can scope selection to its own suffix without that suffix needing to be on the list.
func Select ¶
Select runs the selection chain over cands and returns the chosen candidate. The chain filters by include/exclude, prerelease, cooldown, constraint, and downgrade, then sorts the survivors newest-first and picks the one behind places back. ok is false when nothing survives or behind overshoots.
current is the version in the file: it bounds downgrades and, via a keyword constraint, anchors the allowed range. The sort tie-breaks equal versions by raw tag so the result is deterministic regardless of discovery order.
func SplitVariant ¶
SplitVariant separates a recognized image variant suffix from tag, returning the base (tag without the variant) and the variant ("" when none). It splits on the first dash: "1.27-alpine" -> ("1.27", "alpine"), while a true prerelease is left intact: "2.0.0-rc.1" -> ("2.0.0-rc.1", ""). This lets a variant tag be ordered by its numeric core rather than as a (lower-sorting) prerelease.
Types ¶
type Attrs ¶
type Attrs struct {
// Tag is the raw version string, matched by the include/exclude predicates.
Tag string
// Semver is the parsed version. A nil Semver is unorderable and never
// selected.
Semver *Version
// Prerelease marks a candidate the upstream declares a prerelease out of band
// of its tag (e.g. a GitHub release flagged pre-release). It is excluded like
// a semver prerelease and, being an explicit upstream signal, is not waived by
// the qualifier exemption.
Prerelease bool
// PublishedAt is the release time, consulted by cooldown. Zero disables
// cooldown for the candidate.
PublishedAt time.Time
// Assets are the candidate's published asset filenames, matched by the asset
// predicate. Empty for a provider that publishes none (tags, docker, helm).
Assets []string
}
Attrs is the slice of a candidate the selection chain reads. Callers supply an extractor mapping their own candidate type to Attrs, so this package needs no knowledge of (and no import cycle with) the richer candidate model.
type Bound ¶
type Bound int
Bound is a keyword constraint: a ceiling on how far a candidate may move from the current version, expressed in terms of the highest component it may change. BoundMajor is effectively unbounded upward.
type Constraint ¶
type Constraint struct {
// contains filtered or unexported fields
}
Constraint bounds which candidate versions are eligible. It unifies clover's two constraint dialects behind a single Constraint.Allowed predicate:
- keyword (major/minor/patch): a Bound ceiling relative to the current version - the part go-version cannot express, and clover's reason to own this type at all;
- range (>=1.2,<2.0, ~>1.4, =, !=, >, <): delegated to go-version, the Terraform engine, deliberately not npm-style caret/tilde semantics.
A nil *Constraint means unconstrained: Constraint.Allowed reports true for every candidate. This lets the absent-constraint default flow through the selection chain as a no-op without a special case at every call site.
func NewConstraint ¶
func NewConstraint(expr string, current *Version) (*Constraint, error)
NewConstraint compiles a constraint expression. A keyword (major/minor/patch) yields a Bound measured against current; every other expression is parsed as a go-version range. A keyword needs a parseable current to measure against, so a nil current with a keyword expression fails loud - whereas a range ignores current and still works, matching the design's "keyword + unparseable current ⇒ fail-loud; range still works".
func (*Constraint) Allowed ¶
func (c *Constraint) Allowed(candidate *Version) bool
Allowed reports whether candidate satisfies the constraint. A nil receiver is unconstrained and allows everything.
type Option ¶
type Option func(*query)
Option configures Select.
func WithAsset ¶
WithAsset keeps only candidates publishing an asset whose filename matches at least one predicate. With no asset predicates every candidate is eligible.
func WithBareMajor ¶ added in v0.2.10
WithBareMajor treats a single-number version on the line as a major-precision pin rather than a calendar tag, so dotted candidates stay eligible against it (node = "24" selecting 25.1.0). It applies where the file format gives a bare number that meaning (a mise tool pin) and leaves the calendar-tag guard in force everywhere else.
func WithBehind ¶
WithBehind selects the nth candidate behind the newest after all filtering (0 = newest).
func WithConstraint ¶
func WithConstraint(c *Constraint) Option
WithConstraint limits selection to versions the constraint allows.
func WithCooldown ¶
WithCooldown requires a candidate to be at least d old, measured against the time set by WithNow. Without an injected now, or for a candidate with no publish time, cooldown does not apply.
func WithDowngrade ¶
WithDowngrade permits selecting a version older than current.
func WithExact ¶ added in v0.3.0
WithExact pins selection to the one version named: only a candidate whose parsed version equals target (or whose raw tag equals it, ignoring a leading v) survives, and the include/exclude, scheme, asset, prerelease, cooldown, constraint, downgrade, and behind rules are all bypassed - an explicit pin is the user overriding the rules. Empty disables it.
func WithExclude ¶
WithExclude drops candidates matching any predicate.
func WithInclude ¶
WithInclude keeps only candidates matching at least one predicate. With no include predicates every candidate is eligible.
func WithNow ¶
WithNow injects the reference time for cooldown, keeping selection free of a hidden clock and so deterministic.
func WithObserver ¶
WithObserver reports each candidate the chain rejects, together with the Reason. It fires only for skipped candidates, letting a caller surface why a version was passed over (e.g. at debug level) without this package taking on a logging dependency.
func WithPrerelease ¶
WithPrerelease allows prerelease versions, which are otherwise excluded.
func WithQualifier ¶
WithQualifier exempts candidates carrying the same trailing suffix as the line (e.g. "ent" for a 1.15.0-ent pin) from the prerelease gate, so a vendor track that semver reads as a prerelease stays selectable without opening every prerelease. Empty disables the exemption.
type Predicate ¶
Predicate reports whether a raw tag matches. include/exclude are supplied as predicates rather than patterns so this package stays free of the pattern engine that sits above it.
type Reason ¶
type Reason int
Reason explains why the chain rejected a candidate. The zero value, ReasonEligible, means the candidate survived every filter.
const ( ReasonEligible Reason = iota // survived every filter ReasonUnparsable // tag is not a parsable version ReasonFiltered // dropped by include/exclude ReasonScheme // a different version scheme than the line (e.g. calendar tag vs dotted semver) ReasonNoAsset // no published asset matched the asset filter ReasonPrerelease // a prerelease, and they are not allowed ReasonCooldown // younger than the cooldown ReasonConstraint // outside the constraint ReasonDowngrade // older than current, downgrades disallowed ReasonExact // not the version an explicit pin demands )
func SelectReason ¶
func SelectReason[T any]( current *Version, cands []T, attrs func(T) Attrs, opts ...Option, ) (T, Reason, bool)
SelectReason is Select, additionally reporting why no candidate was chosen. When ok is false it returns the binding reason: the latest-stage filter any candidate reached before being rejected (see [bindingReason]), so a caller explains the failure by the constraint that actually stood in the way rather than the one the most tags happened to trip. When ok is true the reason is ReasonEligible.
type Version ¶
Version is the parsed, comparable form of a version string. clover uses hashicorp/go-version directly as its semver value type: it is the de-facto engine (Terraform's own) and the design commits to it, so wrapping it would be indirection paid for a swap nobody has planned.