Skip to content

Commit

Permalink
Add Grafana provider (GoogleCloudPlatform#886)
Browse files Browse the repository at this point in the history
* adding grafana provider

Co-authored-by: Mark Howard <mark.howard@form3.tech>
Co-authored-by: Peter Bücker <peter.buecker@form3.tech>
  • Loading branch information
3 people authored Apr 20, 2021
1 parent 80bfc09 commit a08ca01
Show file tree
Hide file tree
Showing 13 changed files with 434 additions and 1 deletion.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ A CLI tool that generates `tf`/`json` and `tfstate` files based on existing infr
* [Mikrotik](/docs/mikrotik.md)
* [Xen Orchestra](/docs/xen.md)
* [GmailFilter](/docs/gmailfilter.md)
* [Grafana](/docs/grafana.md)
- [Contributing](#contributing)
- [Developing](#developing)
- [Infrastructure](#infrastructure)
Expand Down
44 changes: 44 additions & 0 deletions cmd/provider_cmd_grafana.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2018 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package cmd

import (
"github.com/GoogleCloudPlatform/terraformer/providers/grafana"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"github.com/spf13/cobra"
)

func newCmdGrafanaImporter(options ImportOptions) *cobra.Command {
cmd := &cobra.Command{
Use: "grafana",
Short: "Import current state to Terraform configuration from Grafana",
Long: "Import current state to Terraform configuration from Grafana",
RunE: func(cmd *cobra.Command, args []string) error {
provider := newGrafanaProvider()
err := Import(provider, options, []string{})
if err != nil {
return err
}
return nil
},
}
cmd.AddCommand(listCmd(newGrafanaProvider()))
baseProviderFlags(cmd.PersistentFlags(), &options, "grafana_dashboard", "dashboard=slug1")
return cmd
}

func newGrafanaProvider() terraformutils.ProviderGenerator {
return &grafana.GrafanaProvider{}
}
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ func providerImporterSubcommands() []func(options ImportOptions) *cobra.Command
// Monitoring & System Management
newCmdDatadogImporter,
newCmdNewRelicImporter,
newCmdGrafanaImporter,
// Community
newCmdKeycloakImporter,
newCmdLogzioImporter,
Expand Down
29 changes: 29 additions & 0 deletions docs/grafana.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
### Use with [Grafana](https://grafana.com)

This provider uses the [terraform-provider-grafana](https://registry.terraform.io/providers/grafana/grafana/latest).

#### Example

```
GRAFANA_AUTH=api_token GRAFANA_URL=https://stack.grafana.net ./terraformer import grafana -r=dashboards // Import with Grafana API token
GRAFANA_AUTH=username:password GRAFANA_URL=https://stack.grafana.net ./terraformer import grafana -r=dashboards // Import with HTTP basic auth
```

#### Configuration

| Env variable | Description | Required | Default |
| -------------------------- | -------------------------------------------------------------------- | --- | - |
| GRAFANA_AUTH | API token or HTTP basic auth (if pattern is `username:password`) | yes | - |
| GRAFANA_URL | URL to the Grafana instance, e.g. https://stack.grafana.net | yes | - |
| GRAFANA_ORG_ID | Grafana organisation ID | no | 1 |
| HTTPS_TLS_KEY | Path to TLS key file | no | - |
| HTTPS_TLS_CERT | Path to TLS cert file | no | - |
| HTTPS_CA_CERT | Path to CA cert file | no | - |
| HTTPS_INSECURE_SKIP_VERIFY | Whether to skip TLS certificate validation (1 for true, 0 for false) | no | 0 |

List of supported [Grafana](https://grafana.com) resources:

* `dashboard`
* `grafana_dashboard`
* `folder`
* `grafana_folder`
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -103,7 +103,9 @@ require (
github.com/fastly/go-fastly v1.18.0
github.com/google/go-github/v25 v25.1.3
github.com/gophercloud/gophercloud v0.13.0
github.com/grafana/grafana-api-golang-client v0.0.0-20210218192924-9ccd2365d2a6
github.com/hashicorp/go-azure-helpers v0.10.0
github.com/hashicorp/go-cleanhttp v0.5.1
github.com/hashicorp/go-hclog v0.15.0
github.com/hashicorp/go-plugin v1.4.0
github.com/hashicorp/hcl v1.0.0
Expand Down
4 changes: 4 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -470,6 +470,8 @@ github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/me
github.com/go-test/deep v1.0.1/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/go-test/deep v1.0.3 h1:ZrJSEWsXzPOxaZnFteGEfooLba+ju3FYIbOrS+rQd68=
github.com/go-test/deep v1.0.3/go.mod h1:wGDj63lr65AM2AQyKZd/NYHGb0R+1RLqB8NKt3aSFNA=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b h1:/vQ+oYKu+JoyaMPDsv5FzwuL2wwWBgBbtj/YLCi4LuA=
github.com/gobs/pretty v0.0.0-20180724170744-09732c25a95b/go.mod h1:Xo4aNUOrJnVruqWQJBtW6+bTBDTniY8yZum5rF3b5jw=
github.com/gobuffalo/attrs v0.0.0-20190224210810-a9411de4debd/go.mod h1:4duuawTqi2wkkpB4ePgWMaai6/Kc6WEz83bhFwpHzj0=
github.com/gobuffalo/depgen v0.0.0-20190329151759-d478694a28d3/go.mod h1:3STtPUQYuzV0gBVOY3vy6CfMm/ljR4pABfrTeHNLHUY=
github.com/gobuffalo/depgen v0.1.0/go.mod h1:+ifsuy7fhi15RWncXQQKjWS9JPkdah5sZvtHc2RXGlg=
Expand Down Expand Up @@ -596,6 +598,8 @@ github.com/gorilla/websocket v1.4.0/go.mod h1:E7qHFY5m1UJ88s3WnNqhKjPHQ0heANvMoA
github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc=
github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE=
github.com/grafana/grafana-api-golang-client v0.0.0-20210218192924-9ccd2365d2a6 h1:jFAfnEad6JNc0EFbCGxL75m8GoBG/J7/1fAAXseaIf8=
github.com/grafana/grafana-api-golang-client v0.0.0-20210218192924-9ccd2365d2a6/go.mod h1:jFjwT3lvwl4JKqCw3guRJvlQ1/fmhER1h3Zgix3z7jw=
github.com/gregjones/httpcache v0.0.0-20180305231024-9cad4c3443a7/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA=
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=
Expand Down
67 changes: 67 additions & 0 deletions providers/grafana/dashboard.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
package grafana

import (
"encoding/json"
"fmt"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
gapi "github.com/grafana/grafana-api-golang-client"
)

type DashboardGenerator struct {
GrafanaService
}

func (g *DashboardGenerator) InitResources() error {
client, err := g.buildClient()
if err != nil {
return fmt.Errorf("unable to build grafana client: %v", err)
}

err = g.createDashboardResources(client)
if err != nil {
return err
}

return nil
}

func (g *DashboardGenerator) createDashboardResources(client *gapi.Client) error {
dashboards, err := client.Dashboards()
if err != nil {
return fmt.Errorf("unable to list grafana dashboards: %v", err)
}

for _, dashboard := range dashboards {
// search result doesn't include slug, so need to look up dashboard.
dash, err := client.DashboardByUID(dashboard.UID)
if err != nil {
return fmt.Errorf("unable to read grafana dashboard %s: %v", dashboard.Title, err)
}

configJSON, err := json.MarshalIndent(dash.Model, "", " ")
if err != nil {
return fmt.Errorf("unable to marshal configuration for grafana dashboard %s: %v", dashboard.Title, err)
}

filename := fmt.Sprintf("dashboard-%s.json", dash.Meta.Slug)
resource := terraformutils.NewResource(
dash.Meta.Slug,
dashboard.Title,
"grafana_dashboard",
"grafana",
map[string]string{},
[]string{},
map[string]interface{}{
"config_json": fmt.Sprintf("file(\"data/%s\")", filename),
"folder": dashboard.FolderID,
},
)
resource.DataFiles = map[string][]byte{
filename: configJSON,
}
g.Resources = append(g.Resources, resource)
}

return nil
}
49 changes: 49 additions & 0 deletions providers/grafana/folder.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package grafana

import (
"fmt"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
gapi "github.com/grafana/grafana-api-golang-client"
)

type FolderGenerator struct {
GrafanaService
}

func (g *FolderGenerator) InitResources() error {
client, err := g.buildClient()
if err != nil {
return fmt.Errorf("unable to build grafana client: %v", err)
}

err = g.createFolderResources(client)
if err != nil {
return err
}

return nil
}

func (g *FolderGenerator) createFolderResources(client *gapi.Client) error {
folders, err := client.Folders()
if err != nil {
return fmt.Errorf("unable to list grafana folders: %v", err)
}

for _, folder := range folders {
g.Resources = append(g.Resources, terraformutils.NewResource(
fmt.Sprint(folder.ID),
folder.Title,
"grafana_folder",
"grafana",
map[string]string{
"uid": folder.UID,
},
[]string{},
map[string]interface{}{},
))
}

return nil
}
132 changes: 132 additions & 0 deletions providers/grafana/grafana_provider.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
// Copyright 2018 The Terraformer Authors.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package grafana

import (
"os"
"strconv"

"github.com/GoogleCloudPlatform/terraformer/terraformutils"
"github.com/pkg/errors"
"github.com/zclconf/go-cty/cty"
)

type GrafanaProvider struct { //nolint
terraformutils.Provider
auth string
url string
orgID int
tlsKey string
tlsCert string
caCert string
insecureSkipVerify bool
}

func (p GrafanaProvider) GetResourceConnections() map[string]map[string][]string {
return map[string]map[string][]string{
"grafana_dashboard": {
"grafana_folder": []string{"folder", "id"},
},
}
}

func (p GrafanaProvider) GetProviderData(arg ...string) map[string]interface{} {
return map[string]interface{}{
"provider": map[string]interface{}{
"grafana": map[string]interface{}{
"org_id": p.orgID,
"url": p.url,
"auth": p.auth,
"tls_key": p.tlsKey,
"tls_cert": p.tlsCert,
"ca_cert": p.caCert,
"insecure_skip_verify": p.insecureSkipVerify,
},
},
}
}

func (p *GrafanaProvider) GetConfig() cty.Value {
return cty.ObjectVal(map[string]cty.Value{
"org_id": cty.NumberIntVal(int64(p.orgID)),
"url": cty.StringVal(p.url),
"auth": cty.StringVal(p.auth),
"tls_key": cty.StringVal(p.tlsKey),
"tls_cert": cty.StringVal(p.tlsCert),
"ca_cert": cty.StringVal(p.caCert),
"insecure_skip_verify": cty.BoolVal(p.insecureSkipVerify),
})
}

func (p *GrafanaProvider) Init(args []string) error {
p.auth = os.Getenv("GRAFANA_AUTH")
if p.auth == "" {
return errors.New("Grafana API authentication must be set through `GRAFANA_AUTH` env var, either as an API token or as username:password for HTTP basic auth")
}

p.url = os.Getenv("GRAFANA_URL")
if p.url == "" {
return errors.New("Grafana API URL must be set through `GRAFANA_URL` env var")
}

orgID, err := strconv.Atoi(os.Getenv("GRAFANA_ORG_ID"))
if err != nil {
orgID = 1
}
p.orgID = orgID

p.tlsKey = os.Getenv("HTTPS_TLS_KEY")
p.tlsCert = os.Getenv("HTTPS_TLS_CERT")
p.caCert = os.Getenv("HTTPS_CA_CERT")

if os.Getenv("HTTPS_INSECURE_SKIP_VERIFY") == "1" {
p.insecureSkipVerify = true
}

return nil
}

func (p *GrafanaProvider) GetName() string {
return "grafana"
}

func (p *GrafanaProvider) InitService(serviceName string, verbose bool) error {
var isSupported bool
if _, isSupported = p.GetSupportedService()[serviceName]; !isSupported {
return errors.New(p.GetName() + ": " + serviceName + " not supported service")
}

p.Service = p.GetSupportedService()[serviceName]
p.Service.SetName(serviceName)
p.Service.SetVerbose(verbose)
p.Service.SetProviderName(p.GetName())
p.Service.SetArgs(map[string]interface{}{
"org_id": p.orgID,
"url": p.url,
"auth": p.auth,
"tls_key": p.tlsKey,
"tls_cert": p.tlsCert,
"ca_cert": p.caCert,
"insecure_skip_verify": p.insecureSkipVerify,
})
return nil
}

func (p *GrafanaProvider) GetSupportedService() map[string]terraformutils.ServiceGenerator {
return map[string]terraformutils.ServiceGenerator{
"grafana_dashboard": &DashboardGenerator{},
"grafana_folder": &FolderGenerator{},
}
}
Loading

0 comments on commit a08ca01

Please sign in to comment.