Skip to content

Commit

Permalink
Merge pull request #2 from mpostument/folders-notifications-support
Browse files Browse the repository at this point in the history
Folders and notification support
  • Loading branch information
mpostument authored Feb 5, 2021
2 parents ac40ee4 + 997a482 commit 9556c38
Show file tree
Hide file tree
Showing 10 changed files with 392 additions and 137 deletions.
40 changes: 34 additions & 6 deletions README.MD
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,12 @@ Keep your grafana dashboards in sync.
- [Pull](#pull)
- [Save all dashboards to directory](#save-all-dashboards-to-directory)
- [Save dashboards with specific tags to directory](#save-dashboards-with-specific-tags-to-directory)
- [Save folders configuration to directory](#save-folders-configuration-to-directory)
- [Save notifications configuration to directory](#save-notifications-configuration-to-directory)
- [Push](#push)
- [Push dashboards to grafana](#push-dashboards-to-grafana)
- [Push folders to grafana](#push-folders-to-grafana)
- [Push notifications to grafana](#push-notifications-to-grafana)
- [Global parameters](#global-parameters)
- [Contributing](#contributing)
- [License](#license)
Expand All @@ -27,23 +31,47 @@ Download the latest binary from [releases](https://github.com/mpostument/grafana

#### Save all dashboards to directory

`grafana-sync pull`
`grafana-sync pull-dashboards`
Example:
`grafana-sync pull --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="dashboard" --url http://127.0.0.1:3000`
`grafana-sync pull-dashboards --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="dashboards" --url http://127.0.0.1:3000`

#### Save dashboards with specific tags to directory

`grafana-sync pull`
`grafana-sync pull-dashboards`
Example:
`grafana-sync pull --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="dashboard" --url http://127.0.0.1:3000 --tag=export`
`grafana-sync pull-dashboards --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="dashboards" --url http://127.0.0.1:3000 --tag=export`

#### Save folders configuration to directory

`grafana-sync pull-folders`
Example:
`grafana-sync pull-folders --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="folders" --url http://127.0.0.1:3000`

#### Save notifications configuration to directory

`grafana-sync pull-notifications`
Example:
`grafana-sync pull-notifications --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="notifications" --url http://127.0.0.1:3000`

### Push

#### Push dashboards to grafana

`grafana-sync push`
`grafana-sync push-dashboards`
Example:
`grafana-sync push-dashboards --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="dashboards" --url http://127.0.0.1:3000`

#### Push folders to grafana

`grafana-sync push-folders`
Example:
`grafana-sync push-folders --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="folders" --url http://127.0.0.1:3000`

#### Push notifications to grafana

`grafana-sync push-notifications`
Example:
`grafana-sync push --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="dashboard" --url http://127.0.0.1:3000`
`grafana-sync push-notifications --apikey="eyJrIjoiOWJYTktGNFlCbFVMOG1LY3d6ekN4Mmw4MFgyYU44a1UiLCJuIjoiY29icmEiLCJpZCI6MX0=" --directory="notifications" --url http://127.0.0.1:3000`

## Global parameters

Expand Down
84 changes: 75 additions & 9 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,9 @@ var rootCmd = &cobra.Command{
Long: `Root command for grafana interaction.`,
}

var pullCmd = &cobra.Command{
Use: "pull",
Short: "Pull grafana dashboards in to directory",
var pullDashboardsCmd = &cobra.Command{
Use: "pull-dashboards",
Short: "Pull grafana dashboards in to the directory",
Long: `Save to the directory grafana dashboards.
Directory name specified by flag --directory. If flag --tags is used,
additional directory will be created with tag name creating structure like directory/tag`,
Expand All @@ -46,19 +46,81 @@ additional directory will be created with tag name creating structure like direc
apiKey := viper.GetString("apikey")
directory, _ := cmd.Flags().GetString("directory")
tag, _ := cmd.Flags().GetString("tag")
grafana.PullDashboard(url, apiKey, directory, tag)
if err := grafana.PullDashboard(url, apiKey, directory, tag); err != nil {
log.Fatalln("Pull dashboards command failed", err)
}
},
}

var pushCmd = &cobra.Command{
Use: "push",
var pushDashboardsCmd = &cobra.Command{
Use: "push-dashboards",
Short: "Push grafana dashboards from directory",
Long: `Read json with dashboards description and publish to grafana.`,
Run: func(cmd *cobra.Command, args []string) {
url, _ := cmd.Flags().GetString("url")
apiKey, _ := cmd.Flags().GetString("apikey")
directory, _ := cmd.Flags().GetString("directory")
grafana.PushDashboard(url, apiKey, directory)
if err := grafana.PushDashboard(url, apiKey, directory); err != nil {
log.Fatalln("Push dashboards command failed", err)
}
},
}

var pullFoldersCmd = &cobra.Command{
Use: "pull-folders",
Short: "Pull grafana folders json in to the directory",
Long: `Save to the directory grafana folders json.
Directory name specified by flag --directory.`,
Run: func(cmd *cobra.Command, args []string) {
url, _ := cmd.Flags().GetString("url")
apiKey := viper.GetString("apikey")
directory, _ := cmd.Flags().GetString("directory")
if err := grafana.PullFolders(url, apiKey, directory); err != nil {
log.Fatalln("Pull folders command failed", err)
}
},
}

var pushFoldersCmd = &cobra.Command{
Use: "push-folders",
Short: "Read json and create grafana folders",
Long: `Read json with folders description and publish to grafana.`,
Run: func(cmd *cobra.Command, args []string) {
url, _ := cmd.Flags().GetString("url")
apiKey := viper.GetString("apikey")
directory, _ := cmd.Flags().GetString("directory")
if err := grafana.PushFolder(url, apiKey, directory); err != nil {
log.Fatalln("Push folders command failed", err)
}
},
}

var pullNotificationsCmd = &cobra.Command{
Use: "pull-notifications",
Short: "Pull grafana notifications json in to the directory",
Long: `Save to the directory grafana folders json.
Directory name specified by flag --directory.`,
Run: func(cmd *cobra.Command, args []string) {
url, _ := cmd.Flags().GetString("url")
apiKey := viper.GetString("apikey")
directory, _ := cmd.Flags().GetString("directory")
if err := grafana.PullNotifications(url, apiKey, directory); err != nil {
log.Fatalln("Pull notifications command failed", err)
}
},
}

var pushNotificationsCmd = &cobra.Command{
Use: "push-notifications",
Short: "Read json and create grafana notifications",
Long: `Read json with notifications description and publish to grafana.`,
Run: func(cmd *cobra.Command, args []string) {
url, _ := cmd.Flags().GetString("url")
apiKey := viper.GetString("apikey")
directory, _ := cmd.Flags().GetString("directory")
if err := grafana.PushNotification(url, apiKey, directory); err != nil {
log.Fatalln("Push notifications command failed", err)
}
},
}

Expand All @@ -82,8 +144,12 @@ func init() {
log.Println(err)
}

rootCmd.AddCommand(pullCmd)
rootCmd.AddCommand(pushCmd)
rootCmd.AddCommand(pullDashboardsCmd)
rootCmd.AddCommand(pushDashboardsCmd)
rootCmd.AddCommand(pullFoldersCmd)
rootCmd.AddCommand(pushFoldersCmd)
rootCmd.AddCommand(pullNotificationsCmd)
rootCmd.AddCommand(pushNotificationsCmd)
}

// initConfig reads in config file and ENV variables if set.
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ module github.com/mpostument/grafana-sync
go 1.15

require (
github.com/grafana-tools/sdk v0.0.0-20201123153837-5fb28a7aa2ef
github.com/grafana-tools/sdk v0.0.0-20210127134634-c831d821bb8a
github.com/mitchellh/go-homedir v1.1.0
github.com/spf13/cobra v1.1.1
github.com/spf13/viper v1.7.1
Expand Down
22 changes: 20 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@ github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+Ce
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/chromedp/cdproto v0.0.0-20210122124816-7a656c010d57 h1:htpyTFarq7OHx9SpkQ+7x20thTQA6JAsgnuMGoPbH4E=
github.com/chromedp/cdproto v0.0.0-20210122124816-7a656c010d57/go.mod h1:55pim6Ht4LJKdVLlyFJV/g++HsEA1hQxPbB5JyNdZC0=
github.com/chromedp/chromedp v0.6.5 h1:hPaDYBpvD2WFicln0ByzV+XRhSOtLgAgsu39O455iWY=
github.com/chromedp/chromedp v0.6.5/go.mod h1:/Q6h52DkrFuvOgmCuR6O3xT5g0bZYoPqjANKBEvQGEY=
github.com/chromedp/sysutil v1.0.0 h1:+ZxhTpfpZlmchB58ih/LBHX52ky7w2VhQVKQMucy3Ic=
github.com/chromedp/sysutil v1.0.0/go.mod h1:kgWmDdq8fTzXYcKIBqIYvRRTnYb9aNS9moAV0xufSww=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.13+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
Expand All @@ -46,6 +52,12 @@ github.com/go-kit/kit v0.8.0/go.mod h1:xBxKIO96dXMWWy0MnWVtmwkA9/13aqxPnvrjFYMA2
github.com/go-logfmt/logfmt v0.3.0/go.mod h1:Qt1PoO58o5twSAckw1HlFXLmHsOX5/0LbT9GBnD5lWE=
github.com/go-logfmt/logfmt v0.4.0/go.mod h1:3RMwSq7FuexP4Kalkev3ejPJsZTpXXBr9+V4qmtdjCk=
github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY=
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
github.com/gobwas/ws v1.0.4 h1:5eXU1CZhpQdq5kXbKb+sECH5Ia5KiO6CYzIzdlVx6Bs=
github.com/gobwas/ws v1.0.4/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM=
github.com/gogo/protobuf v1.1.1/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ=
github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4=
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
Expand All @@ -71,8 +83,8 @@ github.com/gopherjs/gopherjs v0.0.0-20181017120253-0766667cb4d1/go.mod h1:wJfORR
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gosimple/slug v1.1.1 h1:fRu/digW+NMwBIP+RmviTK97Ho/bEj/C9swrCspN3D4=
github.com/gosimple/slug v1.1.1/go.mod h1:ER78kgg1Mv0NQGlXiDe57DpCyfbNywXXZ9mIorhxAf0=
github.com/grafana-tools/sdk v0.0.0-20201123153837-5fb28a7aa2ef h1:pBtpdcXGO5VLXzmOvlnqfxofkw1Ce8CyoV6mAQUJmm4=
github.com/grafana-tools/sdk v0.0.0-20201123153837-5fb28a7aa2ef/go.mod h1:aqBqJVTJmj0MTX9cP8wuReJPte6HyttMDzSS2u8nJwo=
github.com/grafana-tools/sdk v0.0.0-20210127134634-c831d821bb8a h1:UK4jWytfW64zniHvovsS/ykA923QUr6IYa+j24larnI=
github.com/grafana-tools/sdk v0.0.0-20210127134634-c831d821bb8a/go.mod h1:uby+6hPUCRVNG/iAZKCOlaq5YhyK0oKMRke+FDesZdw=
github.com/grpc-ecosystem/go-grpc-middleware v1.0.0/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs=
github.com/grpc-ecosystem/go-grpc-prometheus v1.2.0/go.mod h1:8NvIoxWQoOIhqOTXgfV/d3M/q6VIi02HzZEHgUlZvzk=
github.com/grpc-ecosystem/grpc-gateway v1.9.0/go.mod h1:vNeuVxBJEsws4ogUvrchl83t/GYV9WGTSLVdBhOQFDY=
Expand Down Expand Up @@ -100,6 +112,8 @@ github.com/hashicorp/serf v0.8.2/go.mod h1:6hOLApaqBFA1NXqRQAsxw9QxuDEvNxSQRwA/J
github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM=
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
github.com/jonboulle/clockwork v0.1.0/go.mod h1:Ii8DK3G1RaLaWxj9trq07+26W01tbo22gdxWY5EU2bo=
github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY=
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU=
github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU=
github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo=
Expand All @@ -116,6 +130,8 @@ github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
github.com/magiconair/properties v1.8.1 h1:ZC2Vc7/ZFkGmsVC9KvOjumD+G5lXy2RtTKyzRKO2BQ4=
github.com/magiconair/properties v1.8.1/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ=
github.com/mailru/easyjson v0.7.6 h1:8yTIVnZgCoiM1TgqoeTl+LfU5Jg6/xL3QhGQnimLYnA=
github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU=
github.com/mattn/go-isatty v0.0.3/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4=
github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0=
Expand Down Expand Up @@ -257,6 +273,8 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w
golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0 h1:HyfiK1WMnHj5FXFXatD+Qs1A/xC2Run6RzeW1SyHxpc=
golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210122093101-04d7465088b8 h1:de2yTH1xuxjmGB7i6Z5o2z3RCHVa0XlpSZzjd8Fe6bE=
golang.org/x/sys v0.0.0-20210122093101-04d7465088b8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs=
Expand Down
41 changes: 41 additions & 0 deletions grafana/common.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package grafana

import (
"fmt"
"io/ioutil"
"os"
"path/filepath"
)

func writeToFile(directory string, content []byte, name string, tag string) error {
var (
err error
path string
dashboardFile *os.File
fileName string
)

path = directory
if tag != "" {
path = filepath.Join(path, tag)
}

if _, err = os.Stat(path); os.IsNotExist(err) {
if err = os.MkdirAll(path, 0755); err != nil {
return err
}
}
fileName = fmt.Sprintf("%s/%s.json", path, name)
dashboardFile, err = os.Create(fileName)
if err != nil {
return err
}

defer dashboardFile.Close()

err = ioutil.WriteFile(dashboardFile.Name(), content, os.FileMode(0755))
if err != nil {
return err
}
return nil
}
81 changes: 81 additions & 0 deletions grafana/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package grafana

import (
"context"
"encoding/json"
"fmt"
"io/ioutil"
"log"
"os"
"strings"

"github.com/grafana-tools/sdk"
)

func PullDashboard(grafanaURL string, apiKey string, directory string, tag string) error {
var (
boardLinks []sdk.FoundBoard
rawBoard sdk.Board
meta sdk.BoardProperties
err error
)
ctx := context.Background()

c := sdk.NewClient(grafanaURL, apiKey, sdk.DefaultHTTPClient)

if boardLinks, err = c.Search(ctx, sdk.SearchTag(tag), sdk.SearchType(sdk.SearchTypeDashboard)); err != nil {
return err
}

for _, link := range boardLinks {
if rawBoard, meta, err = c.GetDashboardByUID(ctx, link.UID); err != nil {
log.Printf("%s for %s\n", err, link.URI)
continue
}
rawBoard.ID = 0
b, err := json.Marshal(rawBoard)
if err != nil {
return err
}
if err = writeToFile(directory, b, meta.Slug, tag); err != nil {
return err
}
}
return nil
}

func PushDashboard(grafanaURL string, apiKey string, directory string) error {
var (
filesInDir []os.FileInfo
rawBoard []byte
err error
)

ctx := context.Background()
c := sdk.NewClient(grafanaURL, apiKey, sdk.DefaultHTTPClient)
if filesInDir, err = ioutil.ReadDir(directory); err != nil {
return err
}
for _, file := range filesInDir {
if strings.HasSuffix(file.Name(), ".json") {
if rawBoard, err = ioutil.ReadFile(fmt.Sprintf("%s/%s", directory, file.Name())); err != nil {
log.Println(err)
continue
}
var board sdk.Board
if err = json.Unmarshal(rawBoard, &board); err != nil {
log.Println(err)
continue
}
params := sdk.SetDashboardParams{
FolderID: sdk.DefaultFolderId,
Overwrite: true,
}
if _, err := c.SetDashboard(ctx, board, params); err != nil {
log.Printf("error on importing dashboard %s", board.Title)
continue
}
}
}
return nil
}
Loading

0 comments on commit 9556c38

Please sign in to comment.