Parcourir la source

Refactored the pusher for cleaner code and to easily plug the poller in

Brendan Abolivier il y a 6 ans
Parent
révision
3949cceabe
Signé par: Brendan Abolivier <contact@brendanabolivier.com> ID de la clé GPG: 8EF1500759F70623
3 fichiers modifiés avec 133 ajouts et 168 suppressions
  1. 34
    68
      src/pusher/common/common.go
  2. 35
    40
      src/pusher/poller/poller.go
  3. 64
    60
      src/pusher/webhook/webhook.go

+ 34
- 68
src/pusher/common/common.go Voir le fichier

@@ -1,7 +1,6 @@
1 1
 package common
2 2
 
3 3
 import (
4
-	"io/ioutil"
5 4
 	"strings"
6 5
 
7 6
 	"config"
@@ -11,94 +10,61 @@ import (
11 10
 	"github.com/sirupsen/logrus"
12 11
 )
13 12
 
14
-// PrepareForPush reads the file containing the JSON representation of a
15
-// dashboard, checks if the dashboard is set to be ignored, and if not appends
16
-// its content to a map, which will be later iterated over to push the contents
17
-// it contains to the Grafana API.
18
-// Returns an error if there was an issue reading the file or checking if the
19
-// dashboard it represents is to be ignored.
20
-func PrepareForPush(
21
-	filename string, filesToPush *map[string][]byte, cfg *config.Config,
13
+func FilterIgnored(
14
+	filesToPush *map[string][]byte, cfg *config.Config,
22 15
 ) (err error) {
23
-	// Don't set versions.json to be pushed
24
-	if strings.HasSuffix(filename, "versions.json") {
25
-		return
26
-	}
27
-
28
-	// Read the file's content
29
-	fileContent, err := ioutil.ReadFile(filename)
30
-	if err != nil {
31
-		return
32
-	}
33
-
34
-	// Check if dashboard is ignored
35
-	ignored, err := isIgnored(fileContent, cfg)
36
-	if err != nil {
37
-		return
38
-	}
16
+	for filename, content := range *filesToPush {
17
+		// Don't set versions.json to be pushed
18
+		if strings.HasSuffix(filename, "versions.json") {
19
+			delete(*filesToPush, filename)
20
+			continue
21
+		}
39 22
 
40
-	// Append to the list of contents to push to Grafana
41
-	if !ignored {
42
-		logrus.WithFields(logrus.Fields{
43
-			"filename": filename,
44
-		}).Info("Preparing file to be pushed to Grafana")
23
+		// Check if dashboard is ignored
24
+		ignored, err := isIgnored(content, cfg)
25
+		if err != nil {
26
+			return err
27
+		}
45 28
 
46
-		(*filesToPush)[filename] = fileContent
29
+		if ignored {
30
+			delete(*filesToPush, filename)
31
+		}
47 32
 	}
48 33
 
49 34
 	return
50 35
 }
51 36
 
52
-func PushFiles(filesToPush map[string][]byte, client *grafana.Client) {
37
+func PushFiles(filenames []string, contents map[string][]byte, client *grafana.Client) {
53 38
 	// Push all files to the Grafana API
54
-	for fileToPush, fileContent := range filesToPush {
55
-		if err := client.CreateOrUpdateDashboard(fileContent); err != nil {
39
+	for _, filename := range filenames {
40
+		if err := client.CreateOrUpdateDashboard(contents[filename]); err != nil {
56 41
 			logrus.WithFields(logrus.Fields{
57 42
 				"error":    err,
58
-				"filename": fileToPush,
43
+				"filename": filename,
59 44
 			}).Error("Failed to push the file to Grafana")
60 45
 		}
61 46
 	}
62 47
 }
63 48
 
64
-// DeleteDashboard reads the dashboard described in a given file and, if the file
65
-// isn't set to be ignored, delete the corresponding dashboard from Grafana.
66
-// Returns an error if there was an issue reading the file's content, checking
67
-// if the dashboard is to be ignored, computing its slug or deleting it from
68
-// Grafana.
69
-func DeleteDashboard(
70
-	filename string, client *grafana.Client, cfg *config.Config,
71
-) (err error) {
72
-	// Don't delete versions.json
73
-	if strings.HasSuffix(filename, "versions.json") {
74
-		return
75
-	}
76
-
77
-	// Read the file's content
78
-	fileContent, err := ioutil.ReadFile(filename)
79
-	if err != nil {
80
-		return
81
-	}
82
-
83
-	// Check if dashboard is ignored
84
-	ignored, err := isIgnored(fileContent, cfg)
85
-	if err != nil {
86
-		return
87
-	}
88
-
89
-	if !ignored {
49
+func DeleteDashboards(filenames []string, contents map[string][]byte, client *grafana.Client) {
50
+	for _, filename := range filenames {
90 51
 		// Retrieve dashboard slug because we need it in the deletion request.
91
-		var slug string
92
-		slug, err = helpers.GetDashboardSlug(fileContent)
52
+		slug, err := helpers.GetDashboardSlug(contents[filename])
93 53
 		if err != nil {
94
-			return
54
+			logrus.WithFields(logrus.Fields{
55
+				"error":    err,
56
+				"filename": filename,
57
+			}).Error("Failed to compute the dahsboard's slug")
95 58
 		}
96 59
 
97
-		// Delete the dashboard
98
-		err = client.DeleteDashboard(slug)
60
+		if err := client.DeleteDashboard(slug); err != nil {
61
+			logrus.WithFields(logrus.Fields{
62
+				"error":    err,
63
+				"filename": filename,
64
+				"slug":     slug,
65
+			}).Error("Failed to remove the dashboard from Grafana")
66
+		}
99 67
 	}
100
-
101
-	return
102 68
 }
103 69
 
104 70
 // isIgnored checks whether the file must be ignored, by checking if there's an

+ 35
- 40
src/pusher/poller/poller.go Voir le fichier

@@ -45,12 +45,15 @@ func poller(
45 45
 		return
46 46
 	}
47 47
 
48
+	filesContents, err := repo.GetFilesContentsAtCommit(latestCommit)
49
+	if err != nil {
50
+		return
51
+	}
52
+
48 53
 	previousCommit := latestCommit
54
+	previousFilesContents := filesContents
49 55
 
50 56
 	for {
51
-		addedOrModified := make([]string, 0)
52
-		removed := make([]string, 0)
53
-
54 57
 		if err = repo.Sync(true); err != nil {
55 58
 			return
56 59
 		}
@@ -61,56 +64,48 @@ func poller(
61 64
 		}
62 65
 
63 66
 		if previousCommit.Hash.String() != latestCommit.Hash.String() {
64
-			lineCounts, err := git.GetFilesLineCountsAtCommit(previousCommit)
67
+			logrus.WithFields(logrus.Fields{
68
+				"previous_hash": previousCommit.Hash.String(),
69
+				"new_hash":      latestCommit.Hash.String(),
70
+			}).Info("New commit(s) detected")
71
+
72
+			filesContents, err = repo.GetFilesContentsAtCommit(latestCommit)
65 73
 			if err != nil {
66 74
 				return err
67 75
 			}
68 76
 
69
-			deltas, err := repo.LineCountsDeltasIgnoreManagerCommits(previousCommit, latestCommit)
77
+			modified, removed, err := repo.GetModifiedAndRemovedFiles(previousCommit, latestCommit)
70 78
 			if err != nil {
71 79
 				return err
72 80
 			}
73 81
 
74
-			for file, delta := range deltas {
75
-				if delta == 0 {
76
-					continue
77
-				}
78
-
79
-				if delta > 0 {
80
-					addedOrModified = append(addedOrModified, file)
81
-				} else if delta < 0 {
82
-					if -delta < lineCounts[file] {
83
-						addedOrModified = append(addedOrModified, file)
84
-					} else {
85
-						removed = append(removed, file)
86
-					}
87
-				}
88
-			}
89
-		}
82
+			mergedContents := mergeContents(modified, removed, filesContents, previousFilesContents)
83
+			common.PushFiles(modified, mergedContents, client)
90 84
 
91
-		filesToPush := make(map[string][]byte)
92
-		for _, filename := range addedOrModified {
93
-			if err = common.PrepareForPush(filename, &filesToPush, cfg); err != nil {
94
-				return err
95
-			}
96
-		}
97
-
98
-		common.PushFiles(filesToPush, client)
99
-
100
-		if delRemoved {
101
-			for _, removedFile := range removed {
102
-				if err = common.DeleteDashboard(
103
-					removedFile, client, cfg,
104
-				); err != nil {
105
-					logrus.WithFields(logrus.Fields{
106
-						"error":    err,
107
-						"filename": removedFile,
108
-					}).Error("Failed to delete the dashboard")
109
-				}
85
+			if delRemoved {
86
+				common.DeleteDashboards(removed, mergedContents, client)
110 87
 			}
111 88
 		}
112 89
 
113 90
 		previousCommit = latestCommit
91
+		previousFilesContents = filesContents
114 92
 		time.Sleep(time.Duration(cfg.Pusher.Config.Interval) * time.Second)
115 93
 	}
116 94
 }
95
+
96
+func mergeContents(
97
+	modified []string, removed []string,
98
+	filesContents map[string][]byte, previousFilesContents map[string][]byte,
99
+) (merged map[string][]byte) {
100
+	merged = make(map[string][]byte)
101
+
102
+	for _, modifiedFile := range modified {
103
+		merged[modifiedFile] = filesContents[modifiedFile]
104
+	}
105
+
106
+	for _, removedFile := range removed {
107
+		merged[removedFile] = previousFilesContents[removedFile]
108
+	}
109
+
110
+	return
111
+}

+ 64
- 60
src/pusher/webhook/webhook.go Voir le fichier

@@ -1,6 +1,9 @@
1 1
 package webhook
2 2
 
3 3
 import (
4
+	"io/ioutil"
5
+	"path/filepath"
6
+
4 7
 	"config"
5 8
 	"git"
6 9
 	"grafana"
@@ -49,6 +52,13 @@ func Setup(conf *config.Config, client *grafana.Client, delRemoved bool) (err er
49 52
 func HandlePush(payload interface{}, header webhooks.Header) {
50 53
 	var err error
51 54
 
55
+	var (
56
+		added    = make([]string, 0)
57
+		modified = make([]string, 0)
58
+		removed  = make([]string, 0)
59
+		contents = make(map[string][]byte)
60
+	)
61
+
52 62
 	// Process the payload using the right structure
53 63
 	pl := payload.(gitlab.PushEventPayload)
54 64
 
@@ -57,25 +67,6 @@ func HandlePush(payload interface{}, header webhooks.Header) {
57 67
 		return
58 68
 	}
59 69
 
60
-	// Clone or pull the repository
61
-	if err = repo.Sync(false); err != nil {
62
-		logrus.WithFields(logrus.Fields{
63
-			"error":      err,
64
-			"repo":       cfg.Git.User + "@" + cfg.Git.URL,
65
-			"clone_path": cfg.Git.ClonePath,
66
-		}).Error("Failed to synchronise the Git repository with the remote")
67
-
68
-		return
69
-	}
70
-
71
-	// Files to push and their contents are stored in a map before being pushed
72
-	// to the Grafana API. We don't push them in the loop iterating over commits
73
-	// because, in the case a file is successively updated by two commits pushed
74
-	// at the same time, it would push the same file several time, which isn't
75
-	// an optimised behaviour.
76
-	filesToPush := make(map[string][]byte)
77
-
78
-	// Iterate over the commits descriptions from the payload
79 70
 	for _, commit := range pl.Commits {
80 71
 		// We don't want to process commits made by the puller
81 72
 		if commit.Author.Email == cfg.Git.CommitsAuthor.Email {
@@ -88,56 +79,52 @@ func HandlePush(payload interface{}, header webhooks.Header) {
88 79
 			continue
89 80
 		}
90 81
 
91
-		// Set all added files to be pushed, except the ones describing a
92
-		// dashboard which name starts with a the prefix specified in the
93
-		// configuration file.
94 82
 		for _, addedFile := range commit.Added {
95
-			if err = common.PrepareForPush(
96
-				addedFile, &filesToPush, cfg,
97
-			); err != nil {
98
-				logrus.WithFields(logrus.Fields{
99
-					"error":    err,
100
-					"filename": addedFile,
101
-				}).Error("Failed to prepare file for push")
102
-
103
-				continue
104
-			}
83
+			added = append(added, addedFile)
105 84
 		}
106 85
 
107
-		// Set all modified files to be pushed, except the ones describing a
108
-		// dashboard which name starts with a the prefix specified in the
109
-		// configuration file.
110 86
 		for _, modifiedFile := range commit.Modified {
111
-			if err = common.PrepareForPush(
112
-				modifiedFile, &filesToPush, cfg,
113
-			); err != nil {
114
-				logrus.WithFields(logrus.Fields{
115
-					"error":    err,
116
-					"filename": modifiedFile,
117
-				}).Error("Failed to prepare file for push")
118
-
119
-				continue
120
-			}
87
+			modified = append(modified, modifiedFile)
121 88
 		}
122 89
 
123
-		// If a file describing a dashboard gets removed from the Git repository,
124
-		// delete the corresponding dashboard on Grafana, but only if the user
125
-		// mentionned they want to do so with the correct command line flag.
126
-		if deleteRemoved {
127
-			for _, removedFile := range commit.Removed {
128
-				if err = common.DeleteDashboard(
129
-					removedFile, grafanaClient, cfg,
130
-				); err != nil {
131
-					logrus.WithFields(logrus.Fields{
132
-						"error":    err,
133
-						"filename": removedFile,
134
-					}).Error("Failed to delete the dashboard")
135
-				}
136
-			}
90
+		for _, removedFile := range commit.Removed {
91
+			removed = append(removed, removedFile)
137 92
 		}
138 93
 	}
139 94
 
140
-	common.PushFiles(filesToPush, grafanaClient)
95
+	if err = getFilesContents(removed, &contents, cfg); err != nil {
96
+		return
97
+	}
98
+
99
+	// Clone or pull the repository
100
+	if err = repo.Sync(false); err != nil {
101
+		logrus.WithFields(logrus.Fields{
102
+			"error":      err,
103
+			"repo":       cfg.Git.User + "@" + cfg.Git.URL,
104
+			"clone_path": cfg.Git.ClonePath,
105
+		}).Error("Failed to synchronise the Git repository with the remote")
106
+
107
+		return
108
+	}
109
+
110
+	if err = getFilesContents(added, &contents, cfg); err != nil {
111
+		return
112
+	}
113
+
114
+	if err = getFilesContents(modified, &contents, cfg); err != nil {
115
+		return
116
+	}
117
+
118
+	if err = common.FilterIgnored(&contents, cfg); err != nil {
119
+		return
120
+	}
121
+
122
+	common.PushFiles(added, contents, grafanaClient)
123
+	common.PushFiles(modified, contents, grafanaClient)
124
+
125
+	if deleteRemoved {
126
+		common.DeleteDashboards(removed, contents, grafanaClient)
127
+	}
141 128
 
142 129
 	// Grafana will auto-update the version number after we pushed the new
143 130
 	// dashboards, so we use the puller mechanic to pull the updated numbers and
@@ -150,3 +137,20 @@ func HandlePush(payload interface{}, header webhooks.Header) {
150 137
 		}).Error("Call to puller returned an error")
151 138
 	}
152 139
 }
140
+
141
+func getFilesContents(
142
+	filenames []string, contents *map[string][]byte, cfg *config.Config,
143
+) (err error) {
144
+	for _, filename := range filenames {
145
+		// Read the file's content
146
+		filePath := filepath.Join(cfg.Git.ClonePath, filename)
147
+		fileContent, err := ioutil.ReadFile(filePath)
148
+		if err != nil {
149
+			return err
150
+		}
151
+
152
+		(*contents)[filename] = fileContent
153
+	}
154
+
155
+	return
156
+}