Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add function to get valid template versions #430

Merged
merged 7 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from 5 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
4 changes: 4 additions & 0 deletions pkg/config/draftconfig_template_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,10 @@ func loadTemplatesWithValidation() error {
return fmt.Errorf("template %s has an invalid version range: %s", path, currTemplate.Versions)
}

if _, err := GetValidTemplateVersions(currTemplate.Versions); err != nil {
return fmt.Errorf("template %s has an invalid or unboud version range %s: %w", path, currTemplate.Versions, err)
}

referenceVarMap := map[string]*BuilderVar{}
activeWhenRefMap := map[string]*BuilderVar{}
allVariables := map[string]*BuilderVar{}
Expand Down
149 changes: 149 additions & 0 deletions pkg/config/draftconfig_utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package config

import (
"fmt"
"slices"
"strings"

"github.com/blang/semver/v4"
)

// GetValidTemplateVersions converts a version range into a list of valid versions.
// expects bounded patch versions eg: >=0.0.1 <=0.0.5 || >0.1.0 <0.1.5 and not unbounded patch versions eg: >=0.0.1 <0.1.5
func GetValidTemplateVersions(inputVersion string) ([]string, error) {
validRanges := strings.Split(inputVersion, "||")
if len(validRanges) == 0 {
return nil, fmt.Errorf("no versions found for input %s", inputVersion)
}

allowedVersions := make([]semver.Version, 0)
invalidVersions := map[string]any{}
for _, validRange := range validRanges {
versions := strings.Split(strings.Trim(validRange, " "), " ")
lessThanVersions := make([]semver.Version, 0)
greaterthanVersions := make([]semver.Version, 0)
for _, version := range versions {
versionCondition := getVersionCondition(version)
semVersion, err := semver.Parse(strings.Trim(version, versionCondition))
if err != nil {
return nil, err
}

switch versionCondition {
case "<=":
lessThanVersions = append(lessThanVersions, semVersion)
case ">=":
greaterthanVersions = append(greaterthanVersions, semVersion)
case "<":
if semVersion.Patch == 0 {
return nil, fmt.Errorf("unbounded version range: %s", validRange)
}
semVersion.Patch--
lessThanVersions = append(lessThanVersions, semVersion)
case ">":
err = semVersion.IncrementPatch()
if err != nil {
return nil, fmt.Errorf("failed to increment patch version: %w", err)
}
greaterthanVersions = append(greaterthanVersions, semVersion)
case "=", "==":
allowedVersions = append(allowedVersions, semVersion)
case "!=", "!":
invalidVersions[semVersion.String()] = true
default:
allowedVersions = append(allowedVersions, semVersion)
}
}

for _, greaterThanVersion := range greaterthanVersions {
hasBound := false
var maxVersionDiff *semver.Version
for _, lessThanVersion := range lessThanVersions {
if greaterThanVersion.GT(lessThanVersion) {
continue
}

versionDiff, err := semver.Parse(fmt.Sprintf("%d.%d.%d", lessThanVersion.Major-greaterThanVersion.Major, lessThanVersion.Minor-greaterThanVersion.Minor, lessThanVersion.Patch-greaterThanVersion.Patch))
if err != nil {
return nil, fmt.Errorf("failed to parse version difference: %w", err)
}

if versionDiff.Major != 0 || versionDiff.Minor != 0 {
continue
}

if maxVersionDiff == nil {
maxVersionDiff = &versionDiff
}

if versionDiff.Patch > maxVersionDiff.Patch {
maxVersionDiff = &versionDiff
}
hasBound = true
}
if !hasBound {
return nil, fmt.Errorf("unbounded version range: %s", validRange)
}

allowedVersions = append(allowedVersions, greaterThanVersion)
for i := 0; i < int(maxVersionDiff.Patch); i++ {
nextPatch, err := semver.Parse(fmt.Sprintf("%d.%d.%d", greaterThanVersion.Major, greaterThanVersion.Minor, int(greaterThanVersion.Patch)+i+1))
if err != nil {
return nil, fmt.Errorf("failed to parse next patch version: %w", err)
}
allowedVersions = append(allowedVersions, nextPatch)
}

}
}

if len(allowedVersions) == 0 {
return nil, fmt.Errorf("no valid versions found for versions %s", inputVersion)
}

// sort versions
slices.SortFunc(allowedVersions, func(i, j semver.Version) int {
if i.GT(j) {
return 1
}
if i.LT(j) {
return -1
}
return 0
})

allowedVersionStrings := make([]string, 0)
alreadyHandledVersions := map[string]any{}
for _, version := range allowedVersions {
if _, ok := alreadyHandledVersions[version.String()]; !ok {
if _, ok := invalidVersions[version.String()]; !ok {
allowedVersionStrings = append(allowedVersionStrings, version.String())
}
alreadyHandledVersions[version.String()] = true
}
}

return allowedVersionStrings, nil
}

func getVersionCondition(version string) string {
if strings.HasPrefix(version, "<=") {
return "<="
} else if strings.HasPrefix(version, ">=") {
return ">="
} else if strings.HasPrefix(version, "==") {
return "=="
} else if strings.HasPrefix(version, "=") {
return "="
} else if strings.HasPrefix(version, ">") {
return ">"
} else if strings.HasPrefix(version, "<") {
return "<"
} else if strings.HasPrefix(version, "!=") {
return "!="
} else if strings.HasPrefix(version, "!") {
return "!"
}

return ""
}
88 changes: 88 additions & 0 deletions pkg/config/draftconfig_utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package config

import (
"testing"

"github.com/blang/semver/v4"
"github.com/stretchr/testify/assert"
)

func TestGetValidTemplateVersions(t *testing.T) {
tests := []struct {
Version string
Valid bool
OutputVersions []string
}{
{
Version: "1.0.0",
Valid: true,
OutputVersions: []string{"1.0.0"},
},
{
Version: "<1.0.0",
Valid: false,
},
{
Version: ">=0.0.1 <=0.0.5 || >0.1.0 <0.1.5",
Valid: true,
OutputVersions: []string{"0.0.1", "0.0.2", "0.0.3", "0.0.4", "0.0.5", "0.1.1", "0.1.2", "0.1.3", "0.1.4"},
},
{
Version: ">=0.0.1 <=0.0.5 || >0.1.0 <0.2.5",
Valid: false,
},
{
Version: ">=0.0.1 <0.0.5",
Valid: true,
OutputVersions: []string{"0.0.1", "0.0.2", "0.0.3", "0.0.4"},
},
{
Version: ">=0.0.1 0.0.5",
Valid: false,
},
{
Version: "<0.1.1",
Valid: false,
},
{
Version: ">=0.0.1 <=0.0.6 !0.0.4",
Valid: true,
OutputVersions: []string{"0.0.1", "0.0.2", "0.0.3", "0.0.5", "0.0.6"},
},
{
Version: ">=0.0.1 <=0.0.6 !0.0.6",
Valid: true,
OutputVersions: []string{"0.0.1", "0.0.2", "0.0.3", "0.0.4", "0.0.5"},
},
{
Version: ">0.0.1 >=0.0.2 <0.0.7 !0.0.6",
Valid: true,
OutputVersions: []string{"0.0.2", "0.0.3", "0.0.4", "0.0.5"},
},
{
Version: ">0.0.1 >=0.0.2 <0.0.7 <=0.0.8 !0.0.6",
Valid: true,
OutputVersions: []string{"0.0.2", "0.0.3", "0.0.4", "0.0.5", "0.0.7", "0.0.8"},
},
{
Version: ">0.0.1 <=0.0.5 || >=0.0.5 <0.0.9 !0.0.6",
Valid: true,
OutputVersions: []string{"0.0.2", "0.0.3", "0.0.4", "0.0.5", "0.0.7", "0.0.8"},
},
}

for _, test := range tests {
versions, err := GetValidTemplateVersions(test.Version)
if test.Valid {
assert.Nil(t, err)
_, err = semver.ParseRange(test.Version)
assert.Nil(t, err)
assert.Equal(t, len(test.OutputVersions), len(versions), test.Version)
for _, ver := range test.OutputVersions {
assert.Contains(t, versions, ver)
}
} else {
assert.NotNil(t, err)
}
}
}
Loading