store: Add blueprint change order tracking

Previously the order that changes were made to blueprints was not being
saved. I worked around this by sorting by timestamp, but it only has 1s
resolution so it is very likely to end up with changes having the same
timestamp, especially when running tests.

This adds a new variable to the Store, it is a list of the commit hashes
for each blueprint, in the order they were made.

Since this is a change to the Store schema the first time the new code
is run with the old store state it needs to populate the commit list, as
best it can, with the existing data. To do that it sorts the changes for
each blueprint by timestamp and version and saves this ordering into the
new BlueprintsCommits list.
This commit is contained in:
Brian C. Lane 2020-03-02 16:36:37 -08:00 committed by Tom Gundersen
parent eeef1e289c
commit aed28ccf72
2 changed files with 75 additions and 25 deletions

View file

@ -29,6 +29,7 @@ import (
"github.com/osbuild/osbuild-composer/internal/rpmmd"
"github.com/osbuild/osbuild-composer/internal/target"
"github.com/coreos/go-semver/semver"
"github.com/google/uuid"
)
@ -40,6 +41,7 @@ type Store struct {
Composes map[uuid.UUID]compose.Compose `json:"composes"`
Sources map[string]SourceConfig `json:"sources"`
BlueprintsChanges map[string]map[string]blueprint.Change `json:"changes"`
BlueprintsCommits map[string][]string `json:"commits"`
mu sync.RWMutex // protects all fields
pendingJobs chan Job
@ -193,6 +195,49 @@ func New(stateDir *string, distroArg distro.Distro, distroRegistryArg distro.Reg
if s.BlueprintsChanges == nil {
s.BlueprintsChanges = make(map[string]map[string]blueprint.Change)
}
if s.BlueprintsCommits == nil {
s.BlueprintsCommits = make(map[string][]string)
}
// Populate BlueprintsCommits for existing blueprints without commit history
// BlueprintsCommits tracks the order of the commits in BlueprintsChanges,
// but may not be in-sync with BlueprintsChanges because it was added later.
// This will sort the existing commits by timestamp and version to update
// the store. BUT since the timestamp resolution is only 1s it is possible
// that the order may be slightly wrong.
for name := range s.BlueprintsChanges {
if len(s.BlueprintsChanges[name]) != len(s.BlueprintsCommits[name]) {
changes := make([]blueprint.Change, 0, len(s.BlueprintsChanges[name]))
for commit := range s.BlueprintsChanges[name] {
changes = append(changes, s.BlueprintsChanges[name][commit])
}
// Sort the changes by Timestamp then version, ascending
sort.Slice(changes, func(i, j int) bool {
if changes[i].Timestamp == changes[j].Timestamp {
vI, err := semver.NewVersion(changes[i].Blueprint.Version)
if err != nil {
vI = semver.New("0.0.0")
}
vJ, err := semver.NewVersion(changes[j].Blueprint.Version)
if err != nil {
vJ = semver.New("0.0.0")
}
return vI.LessThan(*vJ)
}
return changes[i].Timestamp < changes[j].Timestamp
})
commits := make([]string, 0, len(changes))
for _, c := range changes {
commits = append(commits, c.Commit)
}
s.BlueprintsCommits[name] = commits
}
}
return &s
}
@ -320,14 +365,15 @@ func (s *Store) GetBlueprintChange(name string, commit string) *blueprint.Change
return &change
}
// GetBlueprintChanges returns the list of changes, oldest first
func (s *Store) GetBlueprintChanges(name string) []blueprint.Change {
s.mu.RLock()
defer s.mu.RUnlock()
var changes []blueprint.Change
for _, change := range s.BlueprintsChanges[name] {
changes = append(changes, change)
for _, commit := range s.BlueprintsCommits[name] {
changes = append(changes, s.BlueprintsChanges[name][commit])
}
return changes
@ -359,6 +405,8 @@ func (s *Store) PushBlueprint(bp blueprint.Blueprint, commitMsg string) error {
s.BlueprintsChanges[bp.Name] = make(map[string]blueprint.Change)
}
s.BlueprintsChanges[bp.Name][commit] = change
// Keep track of the order of the commits
s.BlueprintsCommits[bp.Name] = append(s.BlueprintsCommits[bp.Name], commit)
if old, ok := s.Blueprints[bp.Name]; ok {
if bp.Version == "" || bp.Version == old.Version {
@ -409,31 +457,32 @@ func (s *Store) TagBlueprint(name string) error {
return errors.New("Unknown blueprint")
}
// Get the latest revision for this blueprint, and the most recent commit
var revision int
var timestamp string
var commit blueprint.Change
for _, c := range s.BlueprintsChanges[name] {
if c.Revision != nil && *c.Revision > revision {
revision = *c.Revision
}
if c.Timestamp > timestamp {
timestamp = c.Timestamp
commit = c
}
}
if len(timestamp) == 0 {
if len(s.BlueprintsCommits[name]) == 0 {
return errors.New("No commits for blueprint")
}
// The most recent commit already has a revision, don't bump it
if commit.Revision != nil {
latest := s.BlueprintsCommits[name][len(s.BlueprintsCommits[name])-1]
// If the most recent commit already has a revision, don't bump it
if s.BlueprintsChanges[name][latest].Revision != nil {
return nil
}
// Get the latest revision for this blueprint
var revision int
var change blueprint.Change
for i := len(s.BlueprintsCommits[name]) - 1; i >= 0; i-- {
commit := s.BlueprintsCommits[name][i]
change = s.BlueprintsChanges[name][commit]
if change.Revision != nil && *change.Revision > revision {
revision = *change.Revision
break
}
}
// Bump the revision (if there was none it will start at 1)
revision++
commit.Revision = &revision
s.BlueprintsChanges[name][commit.Commit] = commit
change.Revision = &revision
s.BlueprintsChanges[name][latest] = change
return nil
})
}

View file

@ -1117,13 +1117,14 @@ func (api *API) blueprintsChangesHandler(writer http.ResponseWriter, request *ht
name = name[1:]
}
bpChanges := api.store.GetBlueprintChanges(name)
// Reverse the changes, newest first
reversed := make([]blueprint.Change, 0, len(bpChanges))
for i := len(bpChanges) - 1; i >= 0; i-- {
reversed = append(reversed, bpChanges[i])
}
if bpChanges != nil {
// Sort the changes by Timestamp, descending
sort.Slice(bpChanges, func(i, j int) bool {
return bpChanges[i].Timestamp > bpChanges[j].Timestamp
})
change := change{
Changes: bpChanges,
Changes: reversed,
Name: name,
Total: len(bpChanges),
}