Skip to content
This repository was archived by the owner on Jul 31, 2025. It is now read-only.

Commit c40da1f

Browse files
Compare the dashboard JSON during import to detect modifications instead of version (note) comparison (#20)
Co-authored-by: Marius Oehler <[email protected]>
1 parent f5d9e4b commit c40da1f

File tree

3 files changed

+161
-146
lines changed

3 files changed

+161
-146
lines changed

pkg/plugin/git_api.go

Lines changed: 39 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@ import (
55
"strings"
66
"time"
77

8+
"gopkg.in/src-d/go-git.v4/plumbing"
9+
810
"github.com/go-git/go-git/v5/plumbing/object"
911
"github.com/grafana/grafana-plugin-sdk-go/backend/log"
1012
ssh2 "golang.org/x/crypto/ssh"
@@ -62,12 +64,13 @@ func createInMemory() (*memory.Storage, billy.Filesystem) {
6264
}
6365

6466
// CloneRepo clones the gitApi.gitUrls repository
65-
func (gitApi GitApi) CloneRepo() (*git.Repository, error) {
66-
// git clone
67+
func (gitApi GitApi) CloneRepo(branchName string) (*git.Repository, error) {
6768
r, err := git.Clone(&gitApi.inMemoryStore, gitApi.inMemoryFileSystem, &git.CloneOptions{
68-
URL: gitApi.gitUrl,
69-
Auth: gitApi.authenticator,
69+
URL: gitApi.gitUrl,
70+
Auth: gitApi.authenticator,
71+
ReferenceName: plumbing.NewBranchReferenceName(branchName),
7072
})
73+
7174
if err != nil {
7275
log.DefaultLogger.Error("clone error", "error", err)
7376
return nil, err
@@ -79,17 +82,20 @@ func (gitApi GitApi) CloneRepo() (*git.Repository, error) {
7982
}
8083

8184
// FetchRepo fetches the given repository
82-
func (gitApi GitApi) FetchRepo(repository git.Repository) {
83-
// fetch repo
85+
func (gitApi GitApi) FetchRepo(repository git.Repository) (error, string) {
86+
8487
log.DefaultLogger.Info("fetching repo")
8588
err := repository.Fetch(&git.FetchOptions{
8689
RemoteName: "origin",
8790
Auth: gitApi.authenticator,
8891
})
89-
if strings.Contains(err.Error(), "already up-to-date") {
90-
log.DefaultLogger.Info("fetching completed", "message", err.Error())
92+
93+
if err == nil {
94+
return nil, ""
95+
} else if strings.Contains(err.Error(), "already up-to-date") {
96+
return err, "up-to-date"
9197
} else {
92-
log.DefaultLogger.Error("fetch error", "fetchMessage", err)
98+
return err, err.Error()
9399
}
94100
}
95101

@@ -143,8 +149,8 @@ func (gitApi GitApi) PushRepo(repository git.Repository) {
143149
}
144150
}
145151

146-
// PullRepo pull the given repository
147-
func (gitApi GitApi) PullRepo(repository git.Repository) {
152+
// PullRepo pull the given repository and returns the latest commit ID
153+
func (gitApi GitApi) PullRepo(repository git.Repository) string {
148154
// pull repo
149155
w, err := repository.Worktree()
150156
if err != nil {
@@ -163,6 +169,28 @@ func (gitApi GitApi) PullRepo(repository git.Repository) {
163169
}
164170
}
165171
}
172+
// retrieves the branch pointed by HEAD
173+
ref, err := repository.Head()
174+
175+
// get the commit object, pointed by ref
176+
commit, err := repository.CommitObject(ref.Hash())
177+
return commit.ID().String()
178+
}
179+
180+
func (gitApi GitApi) GetLatestCommitId(repository git.Repository) (string, error, string) {
181+
// retrieves the branch pointed by HEAD
182+
ref, err := repository.Head()
183+
if err != nil {
184+
return "", err, "Cannot resolve head of repository"
185+
}
186+
187+
// get the commit object, pointed by ref
188+
commit, err := repository.CommitObject(ref.Hash())
189+
if err != nil {
190+
return "", err, "Cannot access commit by hash"
191+
}
192+
193+
return commit.ID().String(), nil, ""
166194
}
167195

168196
// GetFileContent get the given content of a file from the in memory filesystem

pkg/plugin/grafana_api.go

Lines changed: 46 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,8 @@ package plugin
33
import (
44
"context"
55
"encoding/json"
6-
"regexp"
6+
"fmt"
7+
"reflect"
78
"strconv"
89
"strings"
910

@@ -16,6 +17,11 @@ type GrafanaApi struct {
1617
grafanaClient *sdk.Client
1718
}
1819

20+
type DashboardWithCustomFields struct {
21+
sdk.Board
22+
SyncOrigin string `json:"syncOrigin"`
23+
}
24+
1925
// NewGrafanaApi creates a new GrafanaApi instance
2026
func NewGrafanaApi(grafanaURL string, apiToken string) GrafanaApi {
2127
client, _ := sdk.NewClient(grafanaURL, apiToken, sdk.DefaultHTTPClient)
@@ -31,20 +37,21 @@ func (grafanaApi GrafanaApi) SearchDashboardsWithTag(tag string) ([]sdk.FoundBoa
3137
return foundDashboards, err
3238
}
3339

34-
// GetRawDashboardByID return Dashboard by the given UID as raw byte object
35-
func (grafanaApi GrafanaApi) GetRawDashboardByID(uid string) ([]byte, sdk.BoardProperties, error) {
36-
rawDashboard, props, err := grafanaApi.grafanaClient.GetRawDashboardByUID(context.Background(), uid)
37-
return rawDashboard, props, err
38-
}
39-
40-
// GetDashboardObjectByID return Dashboard by the given UID as object
41-
func (grafanaApi GrafanaApi) GetDashboardObjectByID(uid string) (sdk.Board, sdk.BoardProperties, error) {
42-
dashboardObject, props, err := grafanaApi.grafanaClient.GetDashboardByUID(context.Background(), uid)
43-
return dashboardObject, props, err
40+
// GetDashboardObjectByUID return Dashboard by the given UID as object
41+
func (grafanaApi GrafanaApi) GetDashboardObjectByUID(uid string) (sdk.Board, sdk.BoardProperties) {
42+
dashboardObject, dashboardProperties, err := grafanaApi.grafanaClient.GetDashboardByUID(context.Background(), uid)
43+
if err != nil {
44+
dashboardNotFound := strings.Contains(err.Error(), "Dashboard not found")
45+
if !dashboardNotFound {
46+
log.DefaultLogger.Error("get dashboard object error", "error", err.Error())
47+
}
48+
return sdk.Board{}, sdk.BoardProperties{}
49+
}
50+
return dashboardObject, dashboardProperties
4451
}
4552

4653
// CreateOrUpdateDashboardObjectByID create or update the Dashboard with the given dashboard object
47-
func (grafanaApi GrafanaApi) CreateOrUpdateDashboardObjectByID(rawDashboard []byte, folderId int, message string) (sdk.StatusMessage) {
54+
func (grafanaApi GrafanaApi) CreateOrUpdateDashboardObjectByID(rawDashboard []byte, folderId int, message string) sdk.StatusMessage {
4855
statusMessage, err := grafanaApi.grafanaClient.SetRawDashboardWithParam(context.Background(), sdk.RawBoardRequest{
4956
Dashboard: rawDashboard,
5057
Parameters: sdk.SetDashboardParams{
@@ -85,70 +92,41 @@ func (grafanaApi GrafanaApi) GetOrCreateFolderID(folderName string) int {
8592
}
8693

8794
// getDashboardObjectFromRawDashboard get the dashboard object from a raw dashboard json
88-
func getDashboardObjectFromRawDashboard(rawDashboard []byte) sdk.Board {
89-
var dashboard sdk.Board
90-
err := json.Unmarshal(rawDashboard, &dashboard)
95+
func getDashboardObjectFromRawDashboard(rawDashboard []byte) DashboardWithCustomFields {
96+
var dashboardWCF DashboardWithCustomFields
97+
err := json.Unmarshal(rawDashboard, &dashboardWCF)
9198
if err != nil {
9299
log.DefaultLogger.Error("unmarshal raw dashboard error", "error", err.Error())
93100
}
94-
return dashboard
95-
}
96-
97-
// getLatestVersionsFromDashboardById get the latest version information of a dashboard by id
98-
func (grafanaApi GrafanaApi) getLatestVersionsFromDashboardById(dashboardId int) int {
99-
versionResponse, err := grafanaApi.grafanaClient.GetAllDashboardVersions(context.Background(), dashboardId, 1)
100-
if err != nil {
101-
log.DefaultLogger.Error("get dashboard versions error", "error", err.Error())
102-
}
103-
104-
// get version message
105-
latestVersion := versionResponse.Versions[0]
106-
re := regexp.MustCompile(`[-]?\d[\d,]*[\.]?[\d]*`)
107-
idFromVersionMessage := re.FindString(latestVersion.Message)
108-
log.DefaultLogger.Debug("latest ID in version message", "version", idFromVersionMessage)
109-
110-
id, err := strconv.ParseUint(idFromVersionMessage, 10, 32)
111-
if err != nil {
112-
log.DefaultLogger.Error("parsing integer error", "error", err.Error())
113-
}
114-
return int(id)
115-
}
116-
117-
// parseGetDashboardError
118-
func (grafanaApi GrafanaApi) parseGetDashboardError(error error, gitBoardID int, grafanaBoardID int) int {
119-
var grafanaDashboardVersionMessageId int
120-
121-
if error == nil {
122-
// get version message ID
123-
grafanaDashboardVersionMessageId = grafanaApi.getLatestVersionsFromDashboardById(grafanaBoardID)
124-
} else {
125-
containsNoDashboard := strings.Contains(error.Error(), "Dashboard not found")
126-
if containsNoDashboard {
127-
// set message ID from GitHub Board
128-
grafanaDashboardVersionMessageId = gitBoardID
129-
} else {
130-
log.DefaultLogger.Error("get dashboard object error", "error", error.Error())
131-
}
132-
}
133-
134-
return grafanaDashboardVersionMessageId
101+
return dashboardWCF
135102
}
136103

137-
// CreateDashboardObjects set a Dashboard with the given raw dashboard object
138-
func (grafanaApi GrafanaApi) CreateDashboardObjects(fileMap map[string]map[string][]byte) {
104+
// CreateOrUpdateDashboard set a Dashboard with the given raw dashboard object
105+
func (grafanaApi GrafanaApi) CreateOrUpdateDashboard(fileMap map[string]map[string][]byte, currentCommitId string) {
106+
// for each folder
139107
for dashboardDir, dashboardFile := range fileMap {
108+
// get Grafana folder ID or create if not exists
140109
folderID := grafanaApi.GetOrCreateFolderID(dashboardDir)
141-
for dashboardName, rawDashboard := range dashboardFile {
142-
gitDashboardObject := getDashboardObjectFromRawDashboard(rawDashboard)
143-
// get grafana dashboard to compare version with git dashboard
144-
grafanaDashboardObject, _, err := grafanaApi.GetDashboardObjectByID(gitDashboardObject.UID)
145-
grafanaDashboardVersionMessageId := grafanaApi.parseGetDashboardError(err, int(gitDashboardObject.ID), int(grafanaDashboardObject.ID))
146-
147-
if grafanaDashboardVersionMessageId != int(gitDashboardObject.Version) {
148-
grafanaApi.CreateOrUpdateDashboardObjectByID(rawDashboard, folderID, "Synchronized from Version " + strconv.Itoa(int(gitDashboardObject.Version)))
149-
log.DefaultLogger.Debug("Dashboard created", "name", dashboardName)
110+
// for each dashboard within folder
111+
for gitDashboardName, gitRawDashboard := range dashboardFile {
112+
// get dashboards from Git and Grafana for comparison
113+
gitDashboardExtended := getDashboardObjectFromRawDashboard(gitRawDashboard)
114+
grafanaDashboard, _ := grafanaApi.GetDashboardObjectByUID(gitDashboardExtended.UID)
115+
116+
syncOrigin := gitDashboardExtended.SyncOrigin
117+
118+
// 'Version' and 'Dashboard ID' need to be set equal, as they are fundamentally different because of import mechanisms
119+
grafanaDashboard.Version = gitDashboardExtended.Version
120+
grafanaDashboard.ID = gitDashboardExtended.ID
121+
// 'SyncOrigin' need to be set, because custom fields are lost through the import
122+
grafanaDashboardExtended := DashboardWithCustomFields{grafanaDashboard, gitDashboardExtended.SyncOrigin}
123+
124+
if !reflect.DeepEqual(grafanaDashboardExtended, gitDashboardExtended) {
125+
versionMessage := fmt.Sprintf("[SYNC] Synchronized dashboard. Version '%s' from origin '%s' (commit %s).", strconv.Itoa(int(gitDashboardExtended.Version)), syncOrigin, currentCommitId)
126+
grafanaApi.CreateOrUpdateDashboardObjectByID(gitRawDashboard, folderID, versionMessage)
127+
log.DefaultLogger.Debug("Dashboard created", "name", gitDashboardName)
150128
} else {
151-
log.DefaultLogger.Info("Dashboard already up-to-date", "name", dashboardName)
129+
log.DefaultLogger.Debug("Dashboard already up-to-date", "name", gitDashboardName)
152130
}
153131
}
154132
}

0 commit comments

Comments
 (0)