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

Commit 81e2a1c

Browse files
authored
Adding ability to filter for dashboards (#23)
1 parent 4f11d18 commit 81e2a1c

File tree

3 files changed

+98
-17
lines changed

3 files changed

+98
-17
lines changed

README.md

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,10 @@ A possible use case is: push Grafana dashboards from one Grafana instance to a G
77

88
As an example this is useful to stage dashboards from "dev" to "prod" environments.
99

10+
The special thing is that the synchronization of dashboards is based on tags, which can be created by the users themselves. Thus, users can determine when a dashboard is ready for synchronization, e.g. so that it is synchronized from a "dev" to a "prod" environment.
11+
12+
If a dashboard is imported to Grafana but a dashboard with the same name or ID already exists there, it will be overwritten. For security reasons, dashboards **are not deleted** by the application. If a dashboard is obsolete, it must be deleted manually by the user.
13+
1014
## Usage
1115

1216
The application can be used as follows:
@@ -55,10 +59,11 @@ See the following configuration for available configuration options:
5559
enable: true
5660
# the branch to use for exporting dashboards
5761
git-branch: "push-branch"
58-
# only dashboards with match this pattern will be considered in the sync process
62+
# only dashboards with match this pattern will be considered in the sync process.
63+
# this value is a WHITELIST in case it is not empty!!!
5964
filter: ""
6065
# the tag to determine which dashboards should be exported
61-
tag-pattern: "agent"
66+
tag-pattern: "sync"
6267
# whether the sync-tag should be kept during exporting
6368
push-tags: true
6469

@@ -68,7 +73,8 @@ See the following configuration for available configuration options:
6873
enable: true
6974
# the branch to use for importing dashboards
7075
git-branch: "pull-branch"
71-
# only dashboards with match this pattern will be considered in the sync process
76+
# only dashboards with match this pattern will be considered in the sync process.
77+
# this value is a WHITELIST in case it is not empty!!!
7278
filter: ""
7379

7480
## Development

configuration.yml

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,27 @@
22
# example configuration
33
##########################
44

5+
- job-name: "example-job"
56
# API token to interact with the specified Grafana instance
7+
grafana-token: "eyJrIjoiSEp4dzhGdVBxMUhBdm5Db..."
68
# Base URL of the Grafana instance
79
grafana-url: "http://localhost:3000"
810
# SSH-URL of the Git repository to use
9-
private-key-file: "<PRIVATE_KEY_FILE>"
11+
git-repository-url: "<GIT_REPOSITORY_URL>"
1012
# Private key to use for authentication against the Git repository
13+
private-key-file: "<PRIVATE_SSH_KEY>"
1114

1215
# push (export) related configurations
1316
push-configuration:
1417
# whether to export dashboards
1518
enable: true
1619
# the branch to use for exporting dashboards
17-
# only dashboards with match this pattern will be considered in the sync process
20+
git-branch: "push-branch"
21+
# only dashboards with match this pattern will be considered in the sync process.
22+
# this value is a WHITELIST in case it is not empty!
1823
filter: ""
1924
# the tag to determine which dashboards should be exported
25+
tag-pattern: "sync"
2026
# whether the sync-tag should be kept during exporting
2127
push-tags: true
2228

@@ -25,5 +31,7 @@
2531
# whether to import dashboards
2632
enable: true
2733
# the branch to use for importing dashboards
28-
# only dashboards with match this pattern will be considered in the sync process
34+
git-branch: "pull-branch"
35+
# only dashboards with match this pattern will be considered in the sync process.
36+
# this value is a WHITELIST in case it is not empty!
2937
filter: ""

pkg/internal/synchronizer.go

Lines changed: 78 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,10 @@ import (
44
"encoding/json"
55
"fmt"
66
"reflect"
7+
"regexp"
78
"strconv"
89

10+
sdk "github.com/NovatecConsulting/grafana-api-go-sdk"
911
log "github.com/sirupsen/logrus"
1012
)
1113

@@ -81,14 +83,32 @@ func (s *Synchronization) Synchronize(dryRun bool) error {
8183

8284
// Pushs dashboards from the configured Grafana into Git.
8385
func (s *Synchronization) pushDashboards(dryRun bool) error {
86+
configuration := s.options.PushConfiguration
87+
8488
log.WithFields(log.Fields{
85-
"target-branch": s.options.PushConfiguration.GitBranch,
86-
"filter": s.options.PushConfiguration.Filter,
87-
"tag-pattern": s.options.PushConfiguration.TagPattern,
88-
"push-tags": s.options.PushConfiguration.PushTags,
89+
"job": s.options.JobName,
90+
"target-branch": configuration.GitBranch,
91+
"filter": configuration.Filter,
92+
"tag-pattern": configuration.TagPattern,
93+
"push-tags": configuration.PushTags,
8994
}).Info("Starting dashboard synchroization (export) into the Git repository.")
9095

91-
dashboardTag := s.options.PushConfiguration.TagPattern
96+
// initializing the dashboard filter
97+
var regexFilter *regexp.Regexp
98+
var err error
99+
if configuration.Filter != "" {
100+
regexFilter, err = regexp.Compile(configuration.Filter)
101+
if err != nil {
102+
log.WithFields(log.Fields{
103+
"error": err,
104+
"job": s.options.JobName,
105+
"filter": configuration.Filter,
106+
}).Fatal("Invalid filter pattern for the push configuration. Skipping exportation of dashboard.")
107+
return err
108+
}
109+
}
110+
111+
dashboardTag := configuration.TagPattern
92112

93113
resultBoards, err := s.grafanaApi.SearchDashboardsWithTag(dashboardTag)
94114

@@ -100,7 +120,7 @@ func (s *Synchronization) pushDashboards(dryRun bool) error {
100120
log.WithField("amount", len(resultBoards)).Info("Successfully fetched dashboards.")
101121

102122
// clone repo from specific branch
103-
repository, err := s.gitApi.CloneRepo(s.options.PushConfiguration.GitBranch)
123+
repository, err := s.gitApi.CloneRepo(configuration.GitBranch)
104124
if err != nil {
105125
log.WithField("error", err).Fatal("Error while cloning repository.")
106126
return err
@@ -110,8 +130,25 @@ func (s *Synchronization) pushDashboards(dryRun bool) error {
110130
// get dashboard Object and Properties
111131
dashboard, boardProperties := s.grafanaApi.GetDashboardObjectByUID(board.UID)
112132

133+
// synchronize only dashboards matching the filter
134+
if regexFilter != nil {
135+
folderAndTitle := boardProperties.FolderTitle + "/" + dashboard.Title
136+
if regexFilter.FindStringIndex(folderAndTitle) == nil {
137+
log.WithFields(log.Fields{
138+
"dashboard-path": folderAndTitle,
139+
"filter": configuration.Filter,
140+
}).Info("Skipping export because dashboard does not match the specified filter pattern.")
141+
continue
142+
}
143+
}
144+
113145
// delete Tag from dashboard Object
114-
dashboardWithDeletedTag := s.grafanaApi.DeleteTagFromDashboardObjectByID(dashboard, dashboardTag)
146+
var dashboardWithDeletedTag sdk.Board
147+
if configuration.PushTags {
148+
dashboardWithDeletedTag = dashboard
149+
} else {
150+
dashboardWithDeletedTag = s.grafanaApi.DeleteTagFromDashboardObjectByID(dashboard, dashboardTag)
151+
}
115152

116153
// get folder name and id, required for update processes and git folder structure
117154
folderId := boardProperties.FolderID
@@ -142,21 +179,39 @@ func (s *Synchronization) pushDashboards(dryRun bool) error {
142179

143180
log.Info("Successfully pushed dashboards to the remote Git repository.")
144181
} else {
145-
log.WithField("tag-pattern", s.options.PushConfiguration.TagPattern).Info("No dashboards found using the configured tag pattern.")
182+
log.WithField("tag-pattern", configuration.TagPattern).Info("No dashboards found using the configured tag pattern.")
146183
}
147184

148185
return nil
149186
}
150187

151188
// Pulling dashboards from the configured Git and importing them into Grafana.
152189
func (s *Synchronization) pullDashboards(dryRun bool) error {
190+
configuration := s.options.PullConfiguration
191+
153192
log.WithFields(log.Fields{
154-
"target-branch": s.options.PullConfiguration.GitBranch,
155-
"filter": s.options.PullConfiguration.Filter,
193+
"job": s.options.JobName,
194+
"target-branch": configuration.GitBranch,
195+
"filter": configuration.Filter,
156196
}).Info("Starting dashboard synchroization (import) from the Git repository.")
157197

198+
// initializing the dashboard filter
199+
var regexFilter *regexp.Regexp
200+
var err error
201+
if configuration.Filter != "" {
202+
regexFilter, err = regexp.Compile(configuration.Filter)
203+
if err != nil {
204+
log.WithFields(log.Fields{
205+
"error": err,
206+
"job": s.options.JobName,
207+
"filter": configuration.Filter,
208+
}).Fatal("Invalid filter pattern for the pull configuration. Skipping importation of dashboard.")
209+
return err
210+
}
211+
}
212+
158213
// clone and fetch the configured repository
159-
repository, err := s.gitApi.CloneRepo(s.options.PullConfiguration.GitBranch)
214+
repository, err := s.gitApi.CloneRepo(configuration.GitBranch)
160215
if err != nil {
161216
log.WithField("error", err).Fatal("Error while cloning repository.")
162217
return err
@@ -210,6 +265,18 @@ func (s *Synchronization) pullDashboards(dryRun bool) error {
210265
}).Fatal("Failed to unmarshal dashboard.")
211266
}
212267

268+
// synchronize only dashboards matching the filter
269+
if regexFilter != nil {
270+
folderAndTitle := folderName + "/" + dashboard.Title
271+
if regexFilter.FindStringIndex(folderAndTitle) == nil {
272+
log.WithFields(log.Fields{
273+
"dashboard-path": folderAndTitle,
274+
"filter": configuration.Filter,
275+
}).Info("Skipping import because dashboard does not match the specified filter pattern.")
276+
continue
277+
}
278+
}
279+
213280
//gitDashboardExtended := getDashboardObjectFromRawDashboard(gitRawDashboard)
214281
grafanaDashboard, _ := s.grafanaApi.GetDashboardObjectByUID(dashboard.UID)
215282

0 commit comments

Comments
 (0)