Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion integration/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ require (
)

require (
github.com/goccy/go-yaml v1.18.0 // indirect
github.com/goccy/go-yaml v1.19.0 // indirect
github.com/inconshreveable/mousetrap v1.1.0 // indirect
github.com/rivo/uniseg v0.4.7 // indirect
github.com/spf13/cobra v1.10.1 // indirect
Expand Down
4 changes: 2 additions & 2 deletions integration/go.sum
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
github.com/carlmjohnson/requests v0.25.1 h1:17zNRLecxtAjhtdEIV+F+wrYfe+AGZUjWJtpndcOUYA=
github.com/carlmjohnson/requests v0.25.1/go.mod h1:z3UEf8IE4sZxZ78spW6/tLdqBkfCu1Fn4RaYMnZ8SRM=
github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g=
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/goccy/go-yaml v1.19.0 h1:EmkZ9RIsX+Uq4DYFowegAuJo8+xdX3T/2dwNPXbxEYE=
github.com/goccy/go-yaml v1.19.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8=
github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU=
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
Expand Down
74 changes: 42 additions & 32 deletions third_party/go-version/constraint.go
Original file line number Diff line number Diff line change
@@ -1,14 +1,33 @@
// Copyright (c) HashiCorp, Inc.
// Copyright IBM Corp. 2014, 2025
// SPDX-License-Identifier: MPL-2.0

package version

import (
"fmt"
"regexp"
"sort"
"strings"
"sync"
)

var (
constraintRegexp *regexp.Regexp
constraintRegexpOnce sync.Once
)

func getConstraintRegexp() *regexp.Regexp {
constraintRegexpOnce.Do(func() {
// This heavy lifting only happens the first time this function is called
constraintRegexp = regexp.MustCompile(fmt.Sprintf(
`^\s*(%s)\s*(%s)\s*$`,
`<=|>=|!=|~>|<|>|=|`,
VersionRegexpRaw,
))
})
return constraintRegexp
}

// Constraint represents a single constraint for a version, such as
// ">= 1.0".
type Constraint struct {
Expand All @@ -28,38 +47,11 @@ type Constraints []*Constraint

type constraintFunc func(v, c *Version) bool

var constraintOperators map[string]constraintOperation

type constraintOperation struct {
op operator
f constraintFunc
}

var constraintRegexp *regexp.Regexp

func init() {
constraintOperators = map[string]constraintOperation{
"": {op: equal, f: constraintEqual},
"=": {op: equal, f: constraintEqual},
"!=": {op: notEqual, f: constraintNotEqual},
">": {op: greaterThan, f: constraintGreaterThan},
"<": {op: lessThan, f: constraintLessThan},
">=": {op: greaterThanEqual, f: constraintGreaterThanEqual},
"<=": {op: lessThanEqual, f: constraintLessThanEqual},
"~>": {op: pessimistic, f: constraintPessimistic},
}

ops := make([]string, 0, len(constraintOperators))
for k := range constraintOperators {
ops = append(ops, regexp.QuoteMeta(k))
}

constraintRegexp = regexp.MustCompile(fmt.Sprintf(
`^\s*(%s)\s*(%s)\s*$`,
strings.Join(ops, "|"),
VersionRegexpRaw))
}

// NewConstraint will parse one or more constraints from the given
// constraint string. The string must be a comma-separated list of
// constraints.
Expand Down Expand Up @@ -106,7 +98,7 @@ func (cs Constraints) Check(v *Version) bool {
// to '>0.2' it is *NOT* treated as equal.
//
// Missing operator is treated as equal to '=', whitespaces
// are ignored and constraints are sorted before comaparison.
// are ignored and constraints are sorted before comparison.
func (cs Constraints) Equals(c Constraints) bool {
if len(cs) != len(c) {
return false
Expand Down Expand Up @@ -175,17 +167,35 @@ func (c *Constraint) String() string {
}

func parseSingle(v string) (*Constraint, error) {
matches := constraintRegexp.FindStringSubmatch(v)
matches := getConstraintRegexp().FindStringSubmatch(v)
if matches == nil {
return nil, fmt.Errorf("Malformed constraint: %s", v)
return nil, fmt.Errorf("malformed constraint: %s", v)
}

check, err := NewVersion(matches[2])
if err != nil {
return nil, err
}

cop := constraintOperators[matches[1]]
var cop constraintOperation
switch matches[1] {
case "=":
cop = constraintOperation{op: equal, f: constraintEqual}
case "!=":
cop = constraintOperation{op: notEqual, f: constraintNotEqual}
case ">":
cop = constraintOperation{op: greaterThan, f: constraintGreaterThan}
case "<":
cop = constraintOperation{op: lessThan, f: constraintLessThan}
case ">=":
cop = constraintOperation{op: greaterThanEqual, f: constraintGreaterThanEqual}
case "<=":
cop = constraintOperation{op: lessThanEqual, f: constraintLessThanEqual}
case "~>":
cop = constraintOperation{op: pessimistic, f: constraintPessimistic}
default:
cop = constraintOperation{op: equal, f: constraintEqual}
}

return &Constraint{
f: cop.f,
Expand Down
78 changes: 51 additions & 27 deletions third_party/go-version/version.go
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
// Copyright (c) HashiCorp, Inc.
// Copyright IBM Corp. 2014, 2025
// SPDX-License-Identifier: MPL-2.0

package version

import (
"bytes"
"database/sql/driver"
"fmt"
"math"
"regexp"
"strconv"
"strings"
"sync"
)

// The compiled regular expression used to test the validity of a version.
var (
versionRegexp *regexp.Regexp
semverRegexp *regexp.Regexp
versionRegexp *regexp.Regexp
versionRegexpOnce sync.Once
semverRegexp *regexp.Regexp
semverRegexpOnce sync.Once
)

func getVersionRegexp() *regexp.Regexp {
versionRegexpOnce.Do(func() {
versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
})
return versionRegexp
}

func getSemverRegexp() *regexp.Regexp {
semverRegexpOnce.Do(func() {
semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$")
})
return semverRegexp
}

// The raw regular expression string used for testing the validity
// of a version.
const (
Expand All @@ -43,36 +59,31 @@ type Version struct {
original string
}

func init() {
versionRegexp = regexp.MustCompile("^" + VersionRegexpRaw + "$")
semverRegexp = regexp.MustCompile("^" + SemverRegexpRaw + "$")
}

// NewVersion parses the given version and returns a new
// Version.
func NewVersion(v string) (*Version, error) {
return newVersion(v, versionRegexp)
return newVersion(v, getVersionRegexp())
}

// NewSemver parses the given version and returns a new
// Version that adheres strictly to SemVer specs
// https://semver.org/
func NewSemver(v string) (*Version, error) {
return newVersion(v, semverRegexp)
return newVersion(v, getSemverRegexp())
}

func newVersion(v string, pattern *regexp.Regexp) (*Version, error) {
matches := pattern.FindStringSubmatch(v)
if matches == nil {
return nil, fmt.Errorf("Malformed version: %s", v)
return nil, fmt.Errorf("malformed version: %s", v)
}
segmentsStr := strings.Split(matches[1], ".")
segments := make([]int64, len(segmentsStr))
for i, str := range segmentsStr {
val, err := strconv.ParseInt(str, 10, 64)
if err != nil {
return nil, fmt.Errorf(
"Error parsing version: %s", err)
"error parsing version: %s", err)
}

segments[i] = val
Expand Down Expand Up @@ -143,11 +154,14 @@ func (v *Version) Compare(other *Version) int {
// Get the highest specificity (hS), or if they're equal, just use segmentSelf length
lenSelf := len(segmentsSelf)
lenOther := len(segmentsOther)
hS := max(lenSelf, lenOther)
hS := lenSelf
if lenSelf < lenOther {
hS = lenOther
}
// Compare the segments
// Because a constraint could have more/less specificity than the version it's
// checking, we need to account for a lopsided or jagged comparison
for i := range hS {
for i := 0; i < hS; i++ {
if i > lenSelf-1 {
// This means Self had the lower specificity
// Check to see if the remaining segments in Other are all zeros
Expand All @@ -172,7 +186,7 @@ func (v *Version) Compare(other *Version) int {
} else if lhs < rhs {
return -1
}
// Otherwis, rhs was > lhs, they're not equal
// Otherwise, rhs was > lhs, they're not equal
return 1
}

Expand Down Expand Up @@ -264,7 +278,10 @@ func comparePrereleases(v string, other string) int {
selfPreReleaseLen := len(selfPreReleaseMeta)
otherPreReleaseLen := len(otherPreReleaseMeta)

biggestLen := max(selfPreReleaseLen, otherPreReleaseLen)
biggestLen := otherPreReleaseLen
if selfPreReleaseLen > otherPreReleaseLen {
biggestLen = selfPreReleaseLen
}

// loop for parts to find the first difference
for i := 0; i < biggestLen; i = i + 1 {
Expand Down Expand Up @@ -384,22 +401,29 @@ func (v *Version) Segments64() []int64 {
// missing parts (1.0 => 1.0.0) will be made into a canonicalized form
// as shown in the parenthesized examples.
func (v *Version) String() string {
var buf bytes.Buffer
fmtParts := make([]string, len(v.segments))
return string(v.bytes())
}

func (v *Version) bytes() []byte {
var buf []byte
for i, s := range v.segments {
// We can ignore err here since we've pre-parsed the values in segments
str := strconv.FormatInt(s, 10)
fmtParts[i] = str
if i > 0 {
buf = append(buf, '.')
}
buf = strconv.AppendInt(buf, s, 10)
}
fmt.Fprintf(&buf, "%s", strings.Join(fmtParts, "."))

if v.pre != "" {
fmt.Fprintf(&buf, "-%s", v.pre)
buf = append(buf, '-')
buf = append(buf, v.pre...)
}

if v.metadata != "" {
fmt.Fprintf(&buf, "+%s", v.metadata)
buf = append(buf, '+')
buf = append(buf, v.metadata...)
}

return buf.String()
return buf
}

// Original returns the original parsed version as-is, including any
Expand All @@ -426,7 +450,7 @@ func (v *Version) MarshalText() ([]byte, error) {
}

// Scan implements the sql.Scanner interface.
func (v *Version) Scan(src any) error {
func (v *Version) Scan(src interface{}) error {
switch src := src.(type) {
case string:
return v.UnmarshalText([]byte(src))
Expand Down
2 changes: 1 addition & 1 deletion third_party/go-version/version_collection.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright (c) HashiCorp, Inc.
// Copyright IBM Corp. 2014, 2025
// SPDX-License-Identifier: MPL-2.0

package version
Expand Down
Loading