version

package
v0.3.19 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 16, 2026 License: MIT Imports: 9 Imported by: 0

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

Constants

This section is empty.

Variables

This section is empty.

Functions

func Compare

func Compare(a, b *Version) int

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

func IsVariant(segment string) bool

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

func Qualifier(tag string) string

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

func Select[T any](current *Version, cands []T, attrs func(T) Attrs, opts ...Option) (T, bool)

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

func SplitVariant(tag string) (string, string)

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.

func TooFresh

func TooFresh(now, published time.Time, cooldown time.Duration) bool

TooFresh reports whether something published at published is younger than cooldown, measured against now. It is inert (false) without a cooldown, a reference now, or a publish time, so the track path can reuse the same freshness rule selection applies, passing zero values freely.

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.

const (
	BoundMajor Bound = iota // candidate may change any component
	BoundMinor              // candidate must share the current major
	BoundPatch              // candidate must share the current major and minor
)

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

func WithAsset(preds ...Predicate) Option

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

func WithBareMajor(allow bool) Option

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

func WithBehind(n int) Option

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

func WithCooldown(d time.Duration) Option

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

func WithDowngrade(allow bool) Option

WithDowngrade permits selecting a version older than current.

func WithExact added in v0.3.0

func WithExact(target string) Option

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

func WithExclude(preds ...Predicate) Option

WithExclude drops candidates matching any predicate.

func WithInclude

func WithInclude(preds ...Predicate) Option

WithInclude keeps only candidates matching at least one predicate. With no include predicates every candidate is eligible.

func WithNow

func WithNow(t time.Time) Option

WithNow injects the reference time for cooldown, keeping selection free of a hidden clock and so deterministic.

func WithObserver

func WithObserver(fn func(tag string, reason Reason)) Option

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

func WithPrerelease(allow bool) Option

WithPrerelease allows prerelease versions, which are otherwise excluded.

func WithQualifier

func WithQualifier(q string) Option

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

type Predicate func(tag string) bool

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.

func (Reason) Detail

func (r Reason) Detail() string

Detail is a human phrase explaining a rejection, used to enrich the no-candidate error so a failed run says why. It is empty for reasons that need no elaboration.

func (Reason) String

func (r Reason) String() string

String renders the reason as a short, stable label for logging.

type Version

type Version = goversion.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.

func Parse

func Parse(s string) (*Version, error)

Parse is clover's canonical entry point for turning a version string into a comparable Version. It tolerates a leading v and one-to-three components (go-version pads missing components with zeros). The wrapped error names the offending input so callers need not restate it.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL