From aed28ccf726151e02605215dcf4c5a38f257f293 Mon Sep 17 00:00:00 2001 From: "Brian C. Lane" Date: Mon, 2 Mar 2020 16:36:37 -0800 Subject: [PATCH] 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. --- internal/store/store.go | 89 ++++++++++++++++++++++++++++++++--------- internal/weldr/api.go | 11 ++--- 2 files changed, 75 insertions(+), 25 deletions(-) diff --git a/internal/store/store.go b/internal/store/store.go index 0ec56011d..ce1ac32e3 100644 --- a/internal/store/store.go +++ b/internal/store/store.go @@ -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 }) } diff --git a/internal/weldr/api.go b/internal/weldr/api.go index 925ffaa88..07cf0e5af 100644 --- a/internal/weldr/api.go +++ b/internal/weldr/api.go @@ -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), }