Browse Source

If asked via a CLI flag in the pusher call, delete dashboards which files were removed

Brendan Abolivier 6 years ago
parent
commit
0a3e0104ff
Signed by: Brendan Abolivier <contact@brendanabolivier.com> GPG key ID: 8EF1500759F70623
4 changed files with 72 additions and 9 deletions
  1. 3
    1
      README.md
  2. 8
    0
      src/grafana/dashboards.go
  3. 3
    2
      src/pusher/main.go
  4. 58
    6
      src/pusher/webhook.go

+ 3
- 1
README.md View File

24
 
24
 
25
 It will then call the puller to have all the files up to date. This is mainly done to update the version number of each dashboard, as Grafana updates them automatically when a new or updated dashboard is pushed.
25
 It will then call the puller to have all the files up to date. This is mainly done to update the version number of each dashboard, as Grafana updates them automatically when a new or updated dashboard is pushed.
26
 
26
 
27
-Please note that the pusher currently only pushes new or modified dashboards to the Grafana API. If the file for a dashboard is removed from the Git repository, the dashboard won't be deleted on the Grafana instance.
27
+Please note that the pusher currently only pushes new or modified dashboards to the Grafana API. If the file for a dashboard is removed from the Git repository, the dashboard won't be deleted on the Grafana instance, unless specifically asked (see below for more details).
28
 
28
 
29
 Because it hosts a webserver, the pusher runs as a daemon and never exists unless it `panic`s because of an error, or it is killed (e.g. with `Ctrl+C`).
29
 Because it hosts a webserver, the pusher runs as a daemon and never exists unless it `panic`s because of an error, or it is killed (e.g. with `Ctrl+C`).
30
 
30
 
70
 
70
 
71
 If the `--config` flag isn't present in the command line call, it will default to a `config.yaml` file located in the directory from where the call is made.
71
 If the `--config` flag isn't present in the command line call, it will default to a `config.yaml` file located in the directory from where the call is made.
72
 
72
 
73
+The pusher can also be called with the `--delete-removed` flag which will allows it to check for dashboards which files were removed from the Git repository and delete them from Grafana.
74
+
73
 ## Configure
75
 ## Configure
74
 
76
 
75
 To run either the puller or the pusher, you will need a configuration file first. The simplest way to create one is to copy the `config.example.yaml` file at the root of this repository and replace all of the placeholder values with your own.
77
 To run either the puller or the pusher, you will need a configuration file first. The simplest way to create one is to copy the `config.example.yaml` file at the root of this repository and replace all of the placeholder values with your own.

+ 8
- 0
src/grafana/dashboards.go View File

181
 
181
 
182
 	return
182
 	return
183
 }
183
 }
184
+
185
+// DeleteDashboard deletes the dashboard identified by a given slug on the
186
+// Grafana API.
187
+// Returns an error if the process failed.
188
+func (c *Client) DeleteDashboard(slug string) (err error) {
189
+	_, err = c.request("DELETE", "dashboards/db/"+slug, nil)
190
+	return
191
+}

+ 3
- 2
src/pusher/main.go View File

8
 	"logger"
8
 	"logger"
9
 )
9
 )
10
 
10
 
11
-// The Grafana API client and the config need to be global to the package since
12
-// we need them in the webhook handlers.
11
+// Some variables need to be global to the package since we need them in the
12
+// webhook handlers.
13
 // TODO: Find a better way to pass it to the handlers
13
 // TODO: Find a better way to pass it to the handlers
14
 var (
14
 var (
15
 	grafanaClient *grafana.Client
15
 	grafanaClient *grafana.Client
16
 	cfg           *config.Config
16
 	cfg           *config.Config
17
+	deleteRemoved = flag.Bool("delete-removed", false, "For each file removed from Git, delete the corresponding dashboard on the Grafana API")
17
 )
18
 )
18
 
19
 
19
 func main() {
20
 func main() {

+ 58
- 6
src/pusher/webhook.go View File

52
 		return
52
 		return
53
 	}
53
 	}
54
 
54
 
55
-	// Files to push are stored in a map before being pushed to the Grafana API.
56
-	// We don't push them in the loop iterating over commits because, in the
57
-	// case a file is successively updated by two commits pushed at the same
58
-	// time, it would push the same file several time, which isn't an optimised
59
-	// behaviour.
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)
60
 	filesToPush := make(map[string][]byte)
61
 
61
 
62
 	// Iterate over the commits descriptions from the payload
62
 	// Iterate over the commits descriptions from the payload
100
 			}
100
 			}
101
 		}
101
 		}
102
 
102
 
103
-		// TODO: Remove a dashboard when its file gets deleted?
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
+		}
104
 	}
118
 	}
105
 
119
 
106
 	// Push all files to the Grafana API
120
 	// Push all files to the Grafana API
165
 	return
179
 	return
166
 }
180
 }
167
 
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
+
168
 // isIgnored checks whether the file must be ignored, by checking if there's an
220
 // isIgnored checks whether the file must be ignored, by checking if there's an
169
 // prefix for ignored files set in the configuration file, and if the dashboard
221
 // prefix for ignored files set in the configuration file, and if the dashboard
170
 // described in the file has a name that starts with this prefix. Returns an
222
 // described in the file has a name that starts with this prefix. Returns an