Skip to content

Commit

Permalink
Adding support for Dashboard import/export via uid or folder
Browse files Browse the repository at this point in the history
  • Loading branch information
safaci2000 committed Jun 1, 2021
1 parent 9072a2d commit a8bf102
Show file tree
Hide file tree
Showing 8 changed files with 112 additions and 56 deletions.
6 changes: 3 additions & 3 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@
"program": "${workspaceFolder}",
"args": [
"dash",
"list",
"-f",
"'Stardust Internals'",
"export",
// "-f",
// "'General'",
"-d",
"monitored-query-diagnostics"
// "--context",
Expand Down
104 changes: 70 additions & 34 deletions api/dashboards.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
"regexp"
"strings"

Expand All @@ -26,12 +27,6 @@ type DashboardFilter struct {
DashFilter string //name of dashboard
}

var quoteRegex *regexp.Regexp

func init() {
quoteRegex, _ = regexp.Compile("['\"]+")
}

//GetFolders splits the comma delimited folder list and returns a slice
func (s *DashboardFilter) GetFolders() []string {
if s.FolderFilter == "" {
Expand All @@ -42,6 +37,24 @@ func (s *DashboardFilter) GetFolders() []string {
return strings.Split(s.FolderFilter, ",")
}

func (s DashboardFilter) ValidateDashboard(dashUid string) bool {
if s.DashFilter == "" {
return true
}
return dashUid == s.DashFilter
}

func (s DashboardFilter) Validate(folder, dashUid string) bool {
return s.ValidateDashboard(dashUid) && s.ValidateFolder(folder)
}

func (s DashboardFilter) ValidateFolder(folder string) bool {
if s.FolderFilter == "" {
return true
}
return folder == s.FolderFilter
}

//Update the slug in the board returned
func updateSlug(board *sdk.FoundBoard) {
elements := strings.Split(board.URI, "/")
Expand All @@ -52,13 +65,18 @@ func updateSlug(board *sdk.FoundBoard) {

//ListDashboards: List all dashboards optionally filtered by folder name. If folderFilters
// is blank, defaults to the configured Monitored folders
func ListDashboards(client *sdk.Client, filters *DashboardFilter, query string) []sdk.FoundBoard {
func ListDashboards(client *sdk.Client, filters *DashboardFilter) []sdk.FoundBoard {
ctx := context.Background()
var boardsList []sdk.FoundBoard = make([]sdk.FoundBoard, 0)
boardLinks, err := client.SearchDashboards(ctx, query, false)
boardLinks, err := client.SearchDashboards(ctx, "", false)
if err != nil {
panic(err)
}
//Fallback on defaults
if filters == nil {
filters = &DashboardFilter{}
}

folderFilters := filters.GetFolders()
var validFolder bool = false
var validUid bool = false
Expand Down Expand Up @@ -96,7 +114,7 @@ func ListDashboards(client *sdk.Client, filters *DashboardFilter, query string)
}

//ImportDashboards saves all dashboards matching query to configured location
func ImportDashboards(client *sdk.Client, query string, conf *viper.Viper) []string {
func ImportDashboards(client *sdk.Client, filter DashboardFilter, conf *viper.Viper) []string {
var (
boardLinks []sdk.FoundBoard
rawBoard []byte
Expand All @@ -105,11 +123,7 @@ func ImportDashboards(client *sdk.Client, query string, conf *viper.Viper) []str
)
ctx := context.Background()

filters := DashboardFilter{
DashFilter: strings.Join(config.GetDefaultGrafanaConfig().GetMonitoredFolders(), ","),
}

boardLinks = ListDashboards(client, &filters, query)
boardLinks = ListDashboards(client, &filter)
var boards []string = make([]string, 0)
for _, link := range boardLinks {
if rawBoard, meta, err = client.GetRawDashboardByUID(ctx, link.UID); err != nil {
Expand Down Expand Up @@ -140,7 +154,7 @@ func getFolderNameIDMap(client *sdk.Client, ctx context.Context) map[string]int

//ExportDashboards finds all the dashboards in the configured location and exports them to grafana.
// if the folde doesn't exist, it'll be created.
func ExportDashboards(client *sdk.Client, folderFilters []string, query string, conf *viper.Viper) {
func ExportDashboards(client *sdk.Client, filters DashboardFilter, conf *viper.Viper) {
filesInDir := findAllFiles(getResourcePath(conf, "dashboard"))
ctx := context.Background()
var rawBoard []byte
Expand All @@ -150,37 +164,55 @@ func ExportDashboards(client *sdk.Client, folderFilters []string, query string,
var folderId int

for _, file := range filesInDir {
baseFile := filepath.Base(file)
baseFile = strings.ReplaceAll(baseFile, ".json", "")
if strings.HasSuffix(file, ".json") {
if rawBoard, err = ioutil.ReadFile(file); err != nil {
log.Println(err)
continue
}
var board = make(map[string]interface{})
if err = json.Unmarshal(rawBoard, &board); err != nil {
log.Println(err)
log.Printf("Failed to unmarshall file: %s", file)
continue
}

elements := strings.Split(file, "/")
if len(elements) >= 2 {
folderName = elements[len(elements)-2]
}
if folderName == "" || folderName == DefaultFolderName {
folderId = sdk.DefaultFolderId
folderName = DefaultFolderName
}

if folderName == DefaultFolderName {
folderId = sdk.DefaultFolderId
} else {
if val, ok := folderMap[folderName]; ok {
folderId = val
} else {
folder := sdk.Folder{Title: folderName}
folder, err = client.CreateFolder(ctx, folder)
if err != nil {
panic(err)
createFolder := filters.ValidateFolder(folderName)
validUid := filters.ValidateDashboard(baseFile)

if createFolder && validUid {
folder := sdk.Folder{Title: folderName}
folder, err = client.CreateFolder(ctx, folder)
if err != nil {
panic(err)
}
folderMap[folderName] = folder.ID
folderId = folder.ID
}
folderMap[folderName] = folder.ID
folderId = folder.ID
}
}

var board = make(map[string]interface{})
if err = json.Unmarshal(rawBoard, &board); err != nil {
log.Println(err)
log.Printf("Failed to unmarshall file: %s", file)
//If folder OR slug is filtered, then skip if it doesn't match
if !filters.Validate(folderName, baseFile) {
continue
}

title, err := jsonpath.Read(board, "$.title")

rawTitle := fmt.Sprintf("%v", title)
Expand Down Expand Up @@ -209,21 +241,25 @@ func ExportDashboards(client *sdk.Client, folderFilters []string, query string,

//DeleteAllDashboards clears all current dashboards being monitored. Any folder not white listed
// will not be affected
func DeleteAllDashboards(client *sdk.Client, folderFilters []string) []string {
func DeleteAllDashboards(client *sdk.Client, filter DashboardFilter) []string {
ctx := context.Background()
var dashboards []string = make([]string, 0)

filters := DashboardFilter{
DashFilter: strings.Join(config.GetDefaultGrafanaConfig().GetMonitoredFolders(), ","),
}

items := ListDashboards(client, &filters, "")
items := ListDashboards(client, &filter)
for _, item := range items {
_, err := client.DeleteDashboardByUID(ctx, item.UID)
if err == nil {
dashboards = append(dashboards, item.Title)
if filter.Validate(item.FolderTitle, item.Slug) {
_, err := client.DeleteDashboardByUID(ctx, item.UID)
if err == nil {
dashboards = append(dashboards, item.Title)
}
}
}
return dashboards

}

var quoteRegex *regexp.Regexp

func init() {
quoteRegex, _ = regexp.Compile("['\"]+")
}
6 changes: 5 additions & 1 deletion cmd/contextShow.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ var contextShow = &cobra.Command{
Long: `show contexts.`,
Run: func(cmd *cobra.Command, args []string) {
context, _ := cmd.Flags().GetString("context")
if context == "" {
context = config.GetContext()
}

grafana := config.GetGrafanaConfig(context)
d, err := yaml.Marshal(grafana)
if err != nil {
Expand All @@ -30,5 +34,5 @@ var contextShow = &cobra.Command{
func init() {
context.AddCommand(contextShow)
contextShow.Flags().StringP("context", "c", "", "context")
contextShow.MarkFlagRequired("context")
// contextShow.MarkFlagRequired("context")
}
15 changes: 15 additions & 0 deletions cmd/dashboard.go
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
package cmd

import (
"github.com/netsage-project/grafana-dashboard-manager/api"
"github.com/spf13/cobra"
)

func getDashboardGlobalFlags(cmd *cobra.Command) api.DashboardFilter {
folderFilter, _ := cmd.Flags().GetString("folder")
dashboardFilter, _ := cmd.Flags().GetString("dashboard")

filters := api.DashboardFilter{
FolderFilter: folderFilter,
DashFilter: dashboardFilter,
}

return filters

}

var dashboard = &cobra.Command{
Use: "dashboards",
Aliases: []string{"dash", "dashboard"},
Expand All @@ -14,4 +28,5 @@ var dashboard = &cobra.Command{
func init() {
rootCmd.AddCommand(dashboard)
dashboard.PersistentFlags().StringP("dashboard", "d", "", "filter by dashboard slug")
dashboard.PersistentFlags().StringP("folder", "f", "", "Filter by Folder Name (Quotes in names not supported)")
}
16 changes: 11 additions & 5 deletions cmd/dashboardClear.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,19 @@ var ClearDashboards = &cobra.Command{
Short: "delete all monitored dashboards",
Long: `clear all monitored dashboards from grafana`,
Run: func(cmd *cobra.Command, args []string) {
log.Info("Delete all dashboards")
savedFiles := api.DeleteAllDashboards(client, nil)
filter := getDashboardGlobalFlags(cmd)
deletedDashboards := api.DeleteAllDashboards(client, filter)
tableObj.AppendHeader(table.Row{"type", "filename"})
for _, file := range savedFiles {
tableObj.AppendRow(table.Row{"datasource", file})
for _, file := range deletedDashboards {
tableObj.AppendRow(table.Row{"dashboard", file})
}
if len(deletedDashboards) == 0 {
log.Info("No dashboards were found. 0 dashboards removed")

} else {
log.Infof("%s dashboards were deleted", len(deletedDashboards))
tableObj.Render()
}
tableObj.Render()

},
}
Expand Down
6 changes: 4 additions & 2 deletions cmd/dashboardExport.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ var exportDashboard = &cobra.Command{
Short: "export all dashboards",
Long: `export all dashboards`,
Run: func(cmd *cobra.Command, args []string) {
api.ExportDashboards(client, nil, "", configProvider)

filter := getDashboardGlobalFlags(cmd)
api.ExportDashboards(client, filter, configProvider)

tableObj.AppendHeader(table.Row{"Title", "id", "folder", "UID"})
boards := api.ListDashboards(client, nil, "")
boards := api.ListDashboards(client, &filter)

for _, link := range boards {
tableObj.AppendRow(table.Row{link.Title, link.ID, link.FolderTitle, link.UID})
Expand Down
3 changes: 2 additions & 1 deletion cmd/dashboardImport.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@ var importDashboard = &cobra.Command{
Short: "Import all dashboards",
Long: `Import all dashboards from grafana to local file system`,
Run: func(cmd *cobra.Command, args []string) {
savedFiles := api.ImportDashboards(client, "", configProvider)
filter := getDashboardGlobalFlags(cmd)
savedFiles := api.ImportDashboards(client, filter, configProvider)
log.Infof("Importing dashboards for context: '%s'", config.GetContext())
tableObj.AppendHeader(table.Row{"type", "filename"})
for _, file := range savedFiles {
Expand Down
12 changes: 2 additions & 10 deletions cmd/dashboardList.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,15 +17,8 @@ var listDashboards = &cobra.Command{
Run: func(cmd *cobra.Command, args []string) {
tableObj.AppendHeader(table.Row{"id", "Title", "Slug", "Folder", "UID", "URL"})

folderFilter, _ := cmd.Flags().GetString("folder")
dashboardFilter, _ := cmd.Flags().GetString("dashboard")

filters := api.DashboardFilter{
FolderFilter: folderFilter,
DashFilter: dashboardFilter,
}

boards := api.ListDashboards(client, &filters, "")
filters := getDashboardGlobalFlags(cmd)
boards := api.ListDashboards(client, &filters)

log.Infof("Listing dashboards for context: '%s'", config.GetContext())
for _, link := range boards {
Expand All @@ -45,5 +38,4 @@ var listDashboards = &cobra.Command{

func init() {
dashboard.AddCommand(listDashboards)
listDashboards.Flags().StringP("folder", "f", "", "Filter by Folder Name (Quotes in names not supported)")
}

0 comments on commit a8bf102

Please sign in to comment.