Use semver to enforce blueprint version numbers

This changes osbuild-composer's behavior to match lorax-composer when
encountering invalid versions. Instead of leaving them as-is it will
return a BlueprintError explaining the problem. eg.

"errors": [
    {
        "id": "BlueprintsError",
        "msg": "Invalid 'version', must use Semantic Versioning:  is not in dotted-tri format"
    }
]

This is enforced on new blueprints (including the workspace). If a
previously stored blueprint has an invalid version and a new one is
pushed it will use the new version number instead of trying to bump the
invalid one.

This also moves the version bump logic into blueprint instead of store,
and adds an Initialize function that will make sure that the blueprint
has sane default values for any missing fields.

This includes tests for the Initialize and BumpVersion functions.
This commit is contained in:
Brian C. Lane 2020-01-31 16:01:08 -08:00 committed by Ondřej Budai
parent 8e1bc2b644
commit 58839cf927
12 changed files with 647 additions and 71 deletions

View file

@ -17,8 +17,6 @@ import (
"path/filepath"
"reflect"
"sort"
"strconv"
"strings"
"sync"
"time"
@ -293,20 +291,6 @@ func (s *Store) GetBlueprint(name string) (*blueprint.Blueprint, bool) {
}
}
// cockpit-composer cannot deal with missing "packages" or "modules"
if bp.Packages == nil {
bp.Packages = []blueprint.Package{}
}
if bp.Modules == nil {
bp.Modules = []blueprint.Package{}
}
if bp.Groups == nil {
bp.Groups = []blueprint.Group{}
}
if bp.Version == "" {
bp.Version = "0.0.0"
}
return &bp, inWorkspace
}
@ -319,20 +303,6 @@ func (s *Store) GetBlueprintCommitted(name string) *blueprint.Blueprint {
return nil
}
// cockpit-composer cannot deal with missing "packages" or "modules"
if bp.Packages == nil {
bp.Packages = []blueprint.Package{}
}
if bp.Modules == nil {
bp.Modules = []blueprint.Package{}
}
if bp.Groups == nil {
bp.Groups = []blueprint.Group{}
}
if bp.Version == "" {
bp.Version = "0.0.0"
}
return &bp
}
@ -360,27 +330,19 @@ func (s *Store) GetBlueprintChanges(name string) []blueprint.Change {
return changes
}
func bumpVersion(str string) string {
v := [3]uint64{}
fields := strings.SplitN(str, ".", 3)
for i := 0; i < len(fields); i++ {
if n, err := strconv.ParseUint(fields[i], 10, 64); err == nil {
v[i] = n
} else {
// don't touch strings with invalid versions
return str
}
}
return fmt.Sprintf("%d.%d.%d", v[0], v[1], v[2]+1)
}
func (s *Store) PushBlueprint(bp blueprint.Blueprint, commitMsg string) error {
return s.change(func() error {
commit, err := randomSHA1String()
if err != nil {
return err
}
// Make sure the blueprint has default values and that the version is valid
err = bp.Initialize()
if err != nil {
return err
}
timestamp := time.Now().Format("2006-01-02T15:04:05Z")
change := blueprint.Change{
Commit: commit,
@ -397,7 +359,7 @@ func (s *Store) PushBlueprint(bp blueprint.Blueprint, commitMsg string) error {
if old, ok := s.Blueprints[bp.Name]; ok {
if bp.Version == "" || bp.Version == old.Version {
bp.Version = bumpVersion(old.Version)
bp.BumpVersion(old.Version)
}
}
s.Blueprints[bp.Name] = bp
@ -407,6 +369,12 @@ func (s *Store) PushBlueprint(bp blueprint.Blueprint, commitMsg string) error {
func (s *Store) PushBlueprintToWorkspace(bp blueprint.Blueprint) error {
return s.change(func() error {
// Make sure the blueprint has default values and that the version is valid
err := bp.Initialize()
if err != nil {
return err
}
s.Workspace[bp.Name] = bp
return nil
})

View file

@ -4,31 +4,6 @@ import (
"testing"
)
func TestBumpVersion(t *testing.T) {
cases := []struct {
Version string
Expected string
}{
{"", ""},
{"0", "0.0.1"},
{"0.0", "0.0.1"},
{"0.0.0", "0.0.1"},
{"2.1.3", "2.1.4"},
// don't touch invalid version strings
{"0.0.0.0", "0.0.0.0"},
{"0.a.0", "0.a.0"},
{"foo", "foo"},
}
for _, c := range cases {
result := bumpVersion(c.Version)
if result != c.Expected {
t.Errorf("bumpVersion(%#v) is expected to return %#v, but instead returned %#v", c.Version, c.Expected, result)
}
}
}
func TestRandomSHA1String(t *testing.T) {
hash, err := randomSHA1String()
if err != nil {