diff --git a/pkg/registry/apis/provisioning/repository/changes.go b/pkg/registry/apis/provisioning/repository/changes.go new file mode 100644 index 00000000000..75ff2aa29b9 --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/changes.go @@ -0,0 +1,57 @@ +package repository + +import ( + "fmt" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + +func Changes(source []FileTreeEntry, target *provisioning.ResourceList) ([]FileChange, error) { + lookup := make(map[string]*provisioning.ResourceListItem, len(target.Items)) + for _, item := range target.Items { + if item.Path == "" { + if item.Group != "folder.grafana.app" { + return nil, fmt.Errorf("empty path on a non folder") + } + continue + } + lookup[item.Path] = &item + } + + changes := make([]FileChange, 0, len(source)) + for _, file := range source { + if !file.Blob { + continue // skip folder references? + } + + check, ok := lookup[file.Path] + if ok { + if check.Hash != file.Hash { + changes = append(changes, FileChange{ + Action: FileActionUpdated, + Path: file.Path, + Ref: file.Hash, + PreviousRef: check.Hash, + }) + } + delete(lookup, file.Path) + } else { + changes = append(changes, FileChange{ + Action: FileActionCreated, // or previously ignored/failed + Path: file.Path, + Ref: file.Hash, + }) + } + } + + // File that were previously added, but are not in the current list + for _, v := range lookup { + changes = append(changes, FileChange{ + Path: v.Path, + PreviousRef: v.Hash, + Action: FileActionDeleted, + }) + } + + return changes, nil +} diff --git a/pkg/registry/apis/provisioning/repository/changes_test.go b/pkg/registry/apis/provisioning/repository/changes_test.go new file mode 100644 index 00000000000..642c86ef4d0 --- /dev/null +++ b/pkg/registry/apis/provisioning/repository/changes_test.go @@ -0,0 +1,107 @@ +package repository + +import ( + "encoding/json" + "testing" + + "github.com/stretchr/testify/require" + + provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1" +) + +func TestChanges(t *testing.T) { + t.Run("start the same", func(t *testing.T) { + source, target := getBase(t) + changes, err := Changes(source, target) + require.NoError(t, err) + require.Empty(t, changes) + }) + + t.Run("create a source file", func(t *testing.T) { + source, target := getBase(t) + source = append(source, FileTreeEntry{Path: "muta.json", Hash: "xyz", Blob: true}) + + changes, err := Changes(source, target) + require.NoError(t, err) + require.Len(t, changes, 1) + require.Equal(t, FileChange{ + Action: FileActionCreated, + Path: "muta.json", + Ref: "xyz", + }, changes[0]) + }) + + t.Run("delete a source file", func(t *testing.T) { + source, target := getBase(t) + source = []FileTreeEntry{source[0]} + + changes, err := Changes(source, target) + require.NoError(t, err) + require.Len(t, changes, 1) + require.Equal(t, FileChange{ + Action: FileActionDeleted, + Path: "adsl62h.yaml", + PreviousRef: "ce5d497c4deadde6831162ce8509e2b2b1776237", + }, changes[0]) + }) + + t.Run("modify a file", func(t *testing.T) { + source, target := getBase(t) + source[1].Hash = "different" + + changes, err := Changes(source, target) + require.NoError(t, err) + require.Len(t, changes, 1) + require.Equal(t, FileChange{ + Action: FileActionUpdated, + Path: "adsl62h.yaml", + Ref: "different", + PreviousRef: "ce5d497c4deadde6831162ce8509e2b2b1776237", + }, changes[0]) + }) +} + +func getBase(t *testing.T) (source []FileTreeEntry, target *provisioning.ResourceList) { + target = &provisioning.ResourceList{} + err := json.Unmarshal([]byte(`{ + "kind": "ResourceList", + "apiVersion": "provisioning.grafana.app/v0alpha1", + "metadata": {}, + "items": [ + { + "path": "", + "group": "folder.grafana.app", + "resource": "folders", + "name": "simplelocal-3794ab9", + "hash": "", + "title": "Simple local" + }, + { + "path": "ad4lwp2.yaml", + "group": "dashboard.grafana.app", + "resource": "dashboards", + "name": "ad4lwp2-xofjsuo-mr5blr1zwimlfi0ds0pyrrpd", + "hash": "ca83d64b9c4a23fed975aacdf47e7de8878b4ae0", + "title": "Test two", + "folder": "simplelocal-3794ab9" + }, + { + "path": "adsl62h.yaml", + "group": "dashboard.grafana.app", + "resource": "dashboards", + "name": "adsl62h-hrw-f-fvlt2dghp-gufrc4lisksgmq-c", + "hash": "ce5d497c4deadde6831162ce8509e2b2b1776237", + "title": "Test one", + "folder": "simplelocal-3794ab9" + } + ] + }`), target) + require.NoError(t, err) + + source = []FileTreeEntry{ + {Path: "ad4lwp2.yaml", Hash: "ca83d64b9c4a23fed975aacdf47e7de8878b4ae0", Blob: true}, + {Path: "adsl62h.yaml", Hash: "ce5d497c4deadde6831162ce8509e2b2b1776237", Blob: true}, + } + + return // named values! +}