|
@@ -1,242 +0,0 @@
|
1
|
|
-package main
|
2
|
|
-
|
3
|
|
-import (
|
4
|
|
- "io/ioutil"
|
5
|
|
- "strings"
|
6
|
|
-
|
7
|
|
- "config"
|
8
|
|
- "git"
|
9
|
|
- "grafana/helpers"
|
10
|
|
- puller "puller"
|
11
|
|
-
|
12
|
|
- "github.com/sirupsen/logrus"
|
13
|
|
- "gopkg.in/go-playground/webhooks.v3"
|
14
|
|
- "gopkg.in/go-playground/webhooks.v3/gitlab"
|
15
|
|
-)
|
16
|
|
-
|
17
|
|
-// SetupWebhook creates and exposes a GitLab webhook using a given configuration.
|
18
|
|
-// Returns an error if the webhook couldn't be set up.
|
19
|
|
-func SetupWebhook(cfg *config.Config) error {
|
20
|
|
- hook := gitlab.New(&gitlab.Config{
|
21
|
|
- Secret: cfg.Webhook.Secret,
|
22
|
|
- })
|
23
|
|
- hook.RegisterEvents(HandlePush, gitlab.PushEvents)
|
24
|
|
-
|
25
|
|
- return webhooks.Run(
|
26
|
|
- hook,
|
27
|
|
- cfg.Webhook.Interface+":"+cfg.Webhook.Port,
|
28
|
|
- cfg.Webhook.Path,
|
29
|
|
- )
|
30
|
|
-}
|
31
|
|
-
|
32
|
|
-// HandlePush is called each time a push event is sent by GitLab on the webhook.
|
33
|
|
-func HandlePush(payload interface{}, header webhooks.Header) {
|
34
|
|
- var err error
|
35
|
|
-
|
36
|
|
- // Process the payload using the right structure
|
37
|
|
- pl := payload.(gitlab.PushEventPayload)
|
38
|
|
-
|
39
|
|
- // Only push changes made on master to Grafana
|
40
|
|
- if pl.Ref != "refs/heads/master" {
|
41
|
|
- return
|
42
|
|
- }
|
43
|
|
-
|
44
|
|
- // Clone or pull the repository
|
45
|
|
- if _, err = git.Sync(cfg.Git); err != nil {
|
46
|
|
- logrus.WithFields(logrus.Fields{
|
47
|
|
- "error": err,
|
48
|
|
- "repo": cfg.Git.User + "@" + cfg.Git.URL,
|
49
|
|
- "clone_path": cfg.Git.ClonePath,
|
50
|
|
- }).Error("Failed to synchronise the Git repository with the remote")
|
51
|
|
-
|
52
|
|
- return
|
53
|
|
- }
|
54
|
|
-
|
55
|
|
- // Files to push and their contents are stored in a map before being pushed
|
56
|
|
- // to the Grafana API. We don't push them in the loop iterating over commits
|
57
|
|
- // because, in the case a file is successively updated by two commits pushed
|
58
|
|
- // at the same time, it would push the same file several time, which isn't
|
59
|
|
- // an optimised behaviour.
|
60
|
|
- filesToPush := make(map[string][]byte)
|
61
|
|
-
|
62
|
|
- // Iterate over the commits descriptions from the payload
|
63
|
|
- for _, commit := range pl.Commits {
|
64
|
|
- // We don't want to process commits made by the puller
|
65
|
|
- if commit.Author.Email == cfg.Git.CommitsAuthor.Email {
|
66
|
|
- logrus.WithFields(logrus.Fields{
|
67
|
|
- "hash": commit.ID,
|
68
|
|
- "author_email": commit.Author.Email,
|
69
|
|
- "manager_email": cfg.Git.CommitsAuthor.Email,
|
70
|
|
- }).Info("Commit was made by the manager, skipping")
|
71
|
|
-
|
72
|
|
- continue
|
73
|
|
- }
|
74
|
|
-
|
75
|
|
- // Set all added files to be pushed, except the ones describing a
|
76
|
|
- // dashboard which name starts with a the prefix specified in the
|
77
|
|
- // configuration file.
|
78
|
|
- for _, addedFile := range commit.Added {
|
79
|
|
- if err = prepareForPush(addedFile, &filesToPush); err != nil {
|
80
|
|
- logrus.WithFields(logrus.Fields{
|
81
|
|
- "error": err,
|
82
|
|
- "filename": addedFile,
|
83
|
|
- }).Error("Failed to prepare file for push")
|
84
|
|
-
|
85
|
|
- continue
|
86
|
|
- }
|
87
|
|
- }
|
88
|
|
-
|
89
|
|
- // Set all modified files to be pushed, except the ones describing a
|
90
|
|
- // dashboard which name starts with a the prefix specified in the
|
91
|
|
- // configuration file.
|
92
|
|
- for _, modifiedFile := range commit.Modified {
|
93
|
|
- if err = prepareForPush(modifiedFile, &filesToPush); err != nil {
|
94
|
|
- logrus.WithFields(logrus.Fields{
|
95
|
|
- "error": err,
|
96
|
|
- "filename": modifiedFile,
|
97
|
|
- }).Error("Failed to prepare file for push")
|
98
|
|
-
|
99
|
|
- continue
|
100
|
|
- }
|
101
|
|
- }
|
102
|
|
-
|
103
|
|
- // If a file describing a dashboard gets removed from the Git repository,
|
104
|
|
- // delete the corresponding dashboard on Grafana, but only if the user
|
105
|
|
- // mentionned they want to do so with the correct command line flag.
|
106
|
|
- if *deleteRemoved {
|
107
|
|
- for _, removedFile := range commit.Removed {
|
108
|
|
- if err = deleteDashboard(removedFile); err != nil {
|
109
|
|
- logrus.WithFields(logrus.Fields{
|
110
|
|
- "error": err,
|
111
|
|
- "filename": removedFile,
|
112
|
|
- }).Error("Failed to delete the dashboard")
|
113
|
|
- }
|
114
|
|
-
|
115
|
|
- continue
|
116
|
|
- }
|
117
|
|
- }
|
118
|
|
- }
|
119
|
|
-
|
120
|
|
- // Push all files to the Grafana API
|
121
|
|
- for fileToPush, fileContent := range filesToPush {
|
122
|
|
- if err = grafanaClient.CreateOrUpdateDashboard(fileContent); err != nil {
|
123
|
|
- logrus.WithFields(logrus.Fields{
|
124
|
|
- "error": err,
|
125
|
|
- "filename": fileToPush,
|
126
|
|
- }).Error("Failed to push the file to Grafana")
|
127
|
|
-
|
128
|
|
- continue
|
129
|
|
- }
|
130
|
|
- }
|
131
|
|
-
|
132
|
|
- // Grafana will auto-update the version number after we pushed the new
|
133
|
|
- // dashboards, so we use the puller mechanic to pull the updated numbers and
|
134
|
|
- // commit them in the git repo.
|
135
|
|
- if err = puller.PullGrafanaAndCommit(grafanaClient, cfg); err != nil {
|
136
|
|
- logrus.WithFields(logrus.Fields{
|
137
|
|
- "error": err,
|
138
|
|
- "repo": cfg.Git.User + "@" + cfg.Git.URL,
|
139
|
|
- "clone_path": cfg.Git.ClonePath,
|
140
|
|
- }).Error("Call to puller returned an error")
|
141
|
|
- }
|
142
|
|
-}
|
143
|
|
-
|
144
|
|
-// prepareForPush reads the file containing the JSON representation of a
|
145
|
|
-// dashboard, checks if the dashboard is set to be ignored, and if not appends
|
146
|
|
-// its content to a map, which will be later iterated over to push the contents
|
147
|
|
-// it contains to the Grafana API.
|
148
|
|
-// Returns an error if there was an issue reading the file or checking if the
|
149
|
|
-// dashboard it represents is to be ignored.
|
150
|
|
-func prepareForPush(
|
151
|
|
- filename string, filesToPush *map[string][]byte,
|
152
|
|
-) (err error) {
|
153
|
|
- // Don't set versions.json to be pushed
|
154
|
|
- if strings.HasSuffix(filename, "versions.json") {
|
155
|
|
- return
|
156
|
|
- }
|
157
|
|
-
|
158
|
|
- // Read the file's content
|
159
|
|
- fileContent, err := ioutil.ReadFile(filename)
|
160
|
|
- if err != nil {
|
161
|
|
- return
|
162
|
|
- }
|
163
|
|
-
|
164
|
|
- // Check if dashboard is ignored
|
165
|
|
- ignored, err := isIgnored(fileContent)
|
166
|
|
- if err != nil {
|
167
|
|
- return
|
168
|
|
- }
|
169
|
|
-
|
170
|
|
- // Append to the list of contents to push to Grafana
|
171
|
|
- if !ignored {
|
172
|
|
- logrus.WithFields(logrus.Fields{
|
173
|
|
- "filename": filename,
|
174
|
|
- }).Info("Preparing file to be pushed to Grafana")
|
175
|
|
-
|
176
|
|
- (*filesToPush)[filename] = fileContent
|
177
|
|
- }
|
178
|
|
-
|
179
|
|
- return
|
180
|
|
-}
|
181
|
|
-
|
182
|
|
-// deleteDashboard reads the dashboard described in a given file and, if the file
|
183
|
|
-// isn't set to be ignored, delete the corresponding dashboard from Grafana.
|
184
|
|
-// Returns an error if there was an issue reading the file's content, checking
|
185
|
|
-// if the dashboard is to be ignored, computing its slug or deleting it from
|
186
|
|
-// Grafana.
|
187
|
|
-func deleteDashboard(filename string) (err error) {
|
188
|
|
- // Don't delete versions.json
|
189
|
|
- if strings.HasSuffix(filename, "versions.json") {
|
190
|
|
- return
|
191
|
|
- }
|
192
|
|
-
|
193
|
|
- // Read the file's content
|
194
|
|
- fileContent, err := ioutil.ReadFile(filename)
|
195
|
|
- if err != nil {
|
196
|
|
- return
|
197
|
|
- }
|
198
|
|
-
|
199
|
|
- // Check if dashboard is ignored
|
200
|
|
- ignored, err := isIgnored(fileContent)
|
201
|
|
- if err != nil {
|
202
|
|
- return
|
203
|
|
- }
|
204
|
|
-
|
205
|
|
- if !ignored {
|
206
|
|
- // Retrieve dashboard slug because we need it in the deletion request.
|
207
|
|
- var slug string
|
208
|
|
- slug, err = helpers.GetDashboardSlug(fileContent)
|
209
|
|
- if err != nil {
|
210
|
|
- return
|
211
|
|
- }
|
212
|
|
-
|
213
|
|
- // Delete the dashboard
|
214
|
|
- err = grafanaClient.DeleteDashboard(slug)
|
215
|
|
- }
|
216
|
|
-
|
217
|
|
- return
|
218
|
|
-}
|
219
|
|
-
|
220
|
|
-// isIgnored checks whether the file must be ignored, by checking if there's an
|
221
|
|
-// prefix for ignored files set in the configuration file, and if the dashboard
|
222
|
|
-// described in the file has a name that starts with this prefix. Returns an
|
223
|
|
-// error if there was an issue reading or decoding the file.
|
224
|
|
-func isIgnored(dashboardJSON []byte) (bool, error) {
|
225
|
|
- // If there's no prefix set, no file is ignored
|
226
|
|
- if len(cfg.Grafana.IgnorePrefix) == 0 {
|
227
|
|
- return false, nil
|
228
|
|
- }
|
229
|
|
-
|
230
|
|
- // Parse the file's content to extract its slug
|
231
|
|
- slug, err := helpers.GetDashboardSlug(dashboardJSON)
|
232
|
|
- if err != nil {
|
233
|
|
- return false, err
|
234
|
|
- }
|
235
|
|
-
|
236
|
|
- // Compare the slug against the prefix
|
237
|
|
- if strings.HasPrefix(slug, cfg.Grafana.IgnorePrefix) {
|
238
|
|
- return true, nil
|
239
|
|
- }
|
240
|
|
-
|
241
|
|
- return false, nil
|
242
|
|
-}
|