Browse Source

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

Brendan Abolivier 2 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,7 +24,7 @@ For every push event on the `master` branch of the repository, it will look at t
24 24
 
25 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 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,6 +70,8 @@ You can specify a configuration file via the command line flag `--config`, which
70 70
 
71 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 75
 ## Configure
74 76
 
75 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,3 +181,11 @@ func (c *Client) CreateOrUpdateDashboard(contentJSON []byte) (err error) {
181 181
 
182 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,12 +8,13 @@ import (
8 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 13
 // TODO: Find a better way to pass it to the handlers
14 14
 var (
15 15
 	grafanaClient *grafana.Client
16 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 20
 func main() {

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

@@ -52,11 +52,11 @@ func HandlePush(payload interface{}, header webhooks.Header) {
52 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 60
 	filesToPush := make(map[string][]byte)
61 61
 
62 62
 	// Iterate over the commits descriptions from the payload
@@ -100,7 +100,21 @@ func HandlePush(payload interface{}, header webhooks.Header) {
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 120
 	// Push all files to the Grafana API
@@ -165,6 +179,44 @@ func prepareForPush(
165 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 220
 // isIgnored checks whether the file must be ignored, by checking if there's an
169 221
 // prefix for ignored files set in the configuration file, and if the dashboard
170 222
 // described in the file has a name that starts with this prefix. Returns an