feat: Pin installed services to semver images and normalize registry versions
This commit is contained in:
@@ -63,12 +63,32 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
|
||||
groupedPackages[pkg.Name] = append(groupedPackages[pkg.Name], pkg)
|
||||
}
|
||||
|
||||
// Helper function to normalize version strings for semver package (requires 'v' prefix)
|
||||
formatSemver := func(v string) string {
|
||||
if !strings.HasPrefix(v, "v") {
|
||||
return "v" + v
|
||||
// normalizeSemver expands partial version tags to the full X.Y.Z form,
|
||||
// preserving an optional pre-release suffix (v3 -> 3.0.0, v3.1 -> 3.1.0,
|
||||
// v3.3.3-hotfix -> 3.3.3-hotfix).
|
||||
normalizeSemver := func(v string) string {
|
||||
tag := strings.TrimPrefix(v, "v")
|
||||
prerelease := ""
|
||||
if i := strings.IndexByte(tag, '-'); i >= 0 {
|
||||
prerelease = tag[i:]
|
||||
tag = tag[:i]
|
||||
}
|
||||
return v
|
||||
parts := strings.Split(tag, ".")
|
||||
for len(parts) < 3 {
|
||||
parts = append(parts, "0")
|
||||
}
|
||||
return strings.Join(parts, ".") + prerelease
|
||||
}
|
||||
|
||||
// semverSpecificity counts the dot-separated segments of a version's
|
||||
// major.minor.patch part, so that among versions that normalize equal the most
|
||||
// complete one wins (3.4.0 over 3.4).
|
||||
semverSpecificity := func(v string) int {
|
||||
tag := strings.TrimPrefix(v, "v")
|
||||
if i := strings.IndexByte(tag, '-'); i >= 0 {
|
||||
tag = tag[:i]
|
||||
}
|
||||
return strings.Count(tag, ".")
|
||||
}
|
||||
|
||||
// 3. For each container name, find the package entry with the highest semver
|
||||
@@ -79,15 +99,25 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
|
||||
}
|
||||
|
||||
sort.Slice(pkgs, func(i, j int) bool {
|
||||
vI := formatSemver(pkgs[i].Version)
|
||||
vJ := formatSemver(pkgs[j].Version)
|
||||
nI := normalizeSemver(pkgs[i].Version)
|
||||
nJ := normalizeSemver(pkgs[j].Version)
|
||||
vI := "v" + nI
|
||||
vJ := "v" + nJ
|
||||
|
||||
// Compare using semver package
|
||||
if semver.IsValid(vI) && semver.IsValid(vJ) {
|
||||
return semver.Compare(vI, vJ) > 0
|
||||
if c := semver.Compare(vI, vJ); c != 0 {
|
||||
return c > 0
|
||||
}
|
||||
} else if nI != nJ {
|
||||
return nI > nJ
|
||||
}
|
||||
// Fall back to string comparison if not valid semver
|
||||
return pkgs[i].Version > pkgs[j].Version
|
||||
// Tie-break on specificity (most complete wins)
|
||||
if sI, sJ := semverSpecificity(pkgs[i].Version), semverSpecificity(pkgs[j].Version); sI != sJ {
|
||||
return sI > sJ
|
||||
}
|
||||
// Tie-break on creation time (newest wins)
|
||||
return pkgs[i].CreatedAt > pkgs[j].CreatedAt
|
||||
})
|
||||
|
||||
// First item after descending sort is the highest version
|
||||
@@ -115,7 +145,7 @@ func GetRegistry(w http.ResponseWriter, r *http.Request) {
|
||||
|
||||
item := types.RegistryContainer{
|
||||
Name: pkg.Name,
|
||||
Version: pkg.Version,
|
||||
Version: normalizeSemver(pkg.Version),
|
||||
Image: imageRef,
|
||||
Source: source,
|
||||
CreatedAt: pkg.CreatedAt,
|
||||
|
||||
Reference in New Issue
Block a user