Skip to content

Commit

Permalink
feat: optionally tag exported dashboards (#10)
Browse files Browse the repository at this point in the history
  • Loading branch information
clambin authored Jan 14, 2025
1 parent 35d6113 commit d35163d
Show file tree
Hide file tree
Showing 18 changed files with 174 additions and 4,663 deletions.
4 changes: 3 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ module github.com/clambin/grope

go 1.23

toolchain go1.23.4

require (
github.com/clambin/go-common/charmer v0.2.0
github.com/clambin/go-common/set v0.4.3
github.com/go-openapi/strfmt v0.23.0
github.com/gosimple/slug v1.15.0
github.com/grafana/grafana-openapi-client-go v0.0.0-20241126111151-59d2d35e24eb
github.com/grafana/grafana-openapi-client-go v0.0.0-20250108132429-8d7e1f158f65
github.com/grafana/grafana-operator/v5 v5.15.1
github.com/spf13/cobra v1.8.1
github.com/spf13/viper v1.19.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ github.com/gosimple/slug v1.15.0 h1:wRZHsRrRcs6b0XnxMUBM6WK1U1Vg5B0R7VkIf1Xzobo=
github.com/gosimple/slug v1.15.0/go.mod h1:UiRaFH+GEilHstLUmcBgWcI42viBN7mAb818JrYOeFQ=
github.com/gosimple/unidecode v1.0.1 h1:hZzFTMMqSswvf0LBJZCZgThIZrpDHFXux9KeGmn6T/o=
github.com/gosimple/unidecode v1.0.1/go.mod h1:CP0Cr1Y1kogOtx0bJblKzsVWrqYaqfNOnHzpgWw4Awc=
github.com/grafana/grafana-openapi-client-go v0.0.0-20241126111151-59d2d35e24eb h1:fdtb12RMGDBdQwUuWw9SnBWO2kANZGlfh++tIVBYjbU=
github.com/grafana/grafana-openapi-client-go v0.0.0-20241126111151-59d2d35e24eb/go.mod h1:hiZnMmXc9KXNUlvkV2BKFsiWuIFF/fF4wGgYWEjBitI=
github.com/grafana/grafana-openapi-client-go v0.0.0-20250108132429-8d7e1f158f65 h1:AnfwjPE8TXJO8CX0Q5PvtzGta9Ls3iRASWVV4jHl4KA=
github.com/grafana/grafana-openapi-client-go v0.0.0-20250108132429-8d7e1f158f65/go.mod h1:hiZnMmXc9KXNUlvkV2BKFsiWuIFF/fF4wGgYWEjBitI=
github.com/grafana/grafana-operator/v5 v5.15.1 h1:5oRVXO1rsJNwekUHJaX9UvgW44bii0Pzc1snfpIof3Y=
github.com/grafana/grafana-operator/v5 v5.15.1/go.mod h1:C8o2wwaoZXvjnKag5fqBOToooR6NtvAEb9hFx8aJdMI=
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
Expand Down
6 changes: 3 additions & 3 deletions grope.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,13 @@ package main

import (
"github.com/clambin/go-common/charmer"
"github.com/clambin/grope/internal"
"github.com/clambin/grope/internal/grope"
"os"
)

func main() {
if err := internal.RootCmd.Execute(); err != nil {
charmer.GetLogger(&internal.RootCmd).Error("failed to run", "err", err)
if err := grope.RootCmd.Execute(); err != nil {
charmer.GetLogger(&grope.RootCmd).Error("failed to run", "err", err)
os.Exit(1)
}
}
3 changes: 2 additions & 1 deletion internal/cli.go → internal/grope/cli.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package internal
package grope

import (
"fmt"
Expand Down Expand Up @@ -50,6 +50,7 @@ func init() {
var args = charmer.Arguments{
"debug": {Default: false, Help: "Log debug messages"},
"namespace": {Default: "default", Help: "Namespace for k8s config maps"},
"tag": {Default: "", Help: "Dashboard tag (optional)"},
"grafana.url": {Default: "http://localhost:3000", Help: "Grafana URL"},
"grafana.token": {Default: "", Help: "Grafana API token (must have admin rights)"},
"grafana.operator.label.name": {Default: "dashboards", Help: "label used to select the grafana instance (grafana-operator only)"},
Expand Down
2 changes: 1 addition & 1 deletion internal/cli_test.go → internal/grope/cli_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package internal
package grope

import (
"github.com/spf13/viper"
Expand Down
33 changes: 32 additions & 1 deletion internal/exporter.go → internal/grope/exporter.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package internal
package grope

import (
"cmp"
"errors"
"fmt"
"github.com/go-openapi/strfmt"
goapi "github.com/grafana/grafana-openapi-client-go/client"
Expand All @@ -15,6 +16,7 @@ type exporter struct {
logger *slog.Logger
client *grafanaClient
formatter formatter
tag string
folders bool
}

Expand All @@ -31,6 +33,7 @@ func makeExporter(v *viper.Viper, l *slog.Logger) (*exporter, error) {
return &exporter{
logger: l,
client: client,
tag: v.GetString("tag"),
formatter: formatter{
namespace: cmp.Or(v.GetString("namespace"), "default"),
grafanaLabelName: cmp.Or(v.GetString("grafana.operator.label.name"), "dashboards"),
Expand Down Expand Up @@ -71,6 +74,11 @@ func (e exporter) exportDashboards(w io.Writer, args ...string) error {
if err != nil {
return fmt.Errorf("error fetching dashboard: %w", err)
}
if e.tag != "" {
if err = tagDashboard(dashboard, e.tag); err != nil {
return fmt.Errorf("error tagging dashboard: %w", err)
}
}
if err = e.formatter.formatDashboard(w, dashboard); err != nil {
return fmt.Errorf("error formating dashboard %q: %w", dashboard.Title, err)
}
Expand All @@ -85,3 +93,26 @@ func (e exporter) exportDataSources(w io.Writer) error {
}
return err
}

func tagDashboard(db Dashboard, tag string) error {
jsonModel, ok := db.Model.(map[string]any)
if !ok {
return fmt.Errorf("unexpected model type: %T; expected map[string]any", db.Model)
}
tagsAny, ok := jsonModel["tags"]
if !ok {
return errors.New("dashboard does not contain tags")
}
tags, ok := tagsAny.([]any)
if !ok {
return fmt.Errorf("unexpected tags type: %T; expected []any", tagsAny)
}
for _, t := range tags {
if t.(string) == tag {
return nil
}
}
tags = append(tags, tag)
jsonModel["tags"] = tags
return nil
}
73 changes: 70 additions & 3 deletions internal/exporter_test.go → internal/grope/exporter_test.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package internal
package grope

import (
"bytes"
Expand Down Expand Up @@ -37,6 +37,16 @@ func TestExportDashboards(t *testing.T) {
},
wantErr: assert.NoError,
},
{
name: "tagged",
config: func() *viper.Viper {
v := viper.New()
v.Set("grafana.url", "http://grafana")
v.Set("tag", "grope")
return v
},
wantErr: assert.NoError,
},
{
name: "filtered by name",
config: func() *viper.Viper {
Expand Down Expand Up @@ -117,8 +127,8 @@ func TestExportDashboards(t *testing.T) {
}
exp.client.dashboardClient.dashboardFetcher = fakeDashboardFetcher{
dashboards: map[string]any{
"1": map[string]string{"foo": "bar"},
"2": map[string]string{"foo": "bar"},
"1": map[string]any{"foo": "bar", "tags": []any{}},
"2": map[string]any{"foo": "bar", "tags": []any{}},
},
}

Expand Down Expand Up @@ -234,3 +244,60 @@ func (f fakeDataSourceFetcher) GetDataSources(_ ...datasources.ClientOption) (*d
ok.Payload = f.dataSources
return ok, nil
}

func Test_tagDashboard(t *testing.T) {
tests := []struct {
name string
db Dashboard
wantErr assert.ErrorAssertionFunc
}{
{
name: "valid: no tags",
db: Dashboard{Model: map[string]any{
"tags": []any{},
}},
wantErr: assert.NoError,
},
{
name: "valid: tags",
db: Dashboard{Model: map[string]any{
"tags": []any{"foo", "bar"},
}},
wantErr: assert.NoError,
},
{
name: "valid: grope tag already exists",
db: Dashboard{Model: map[string]any{
"tags": []any{"foo", "bar", "grope"},
}},
wantErr: assert.NoError,
},
{
name: "invalid: tags not present",
db: Dashboard{Model: map[string]any{}},
wantErr: assert.Error,
},
{
name: "invalid: tags invalid type",
db: Dashboard{Model: map[string]any{
"tags": "foo",
}},
wantErr: assert.Error,
},
{
name: "invalid: model invalid type",
db: Dashboard{Model: "124"},
wantErr: assert.Error,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tagDashboard(tt.db, "grope")
tt.wantErr(t, err)

if err == nil {
assert.Contains(t, tt.db.Model.(map[string]any)["tags"], "grope")
}
})
}
}
4 changes: 2 additions & 2 deletions internal/fetcher.go → internal/grope/fetcher.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package internal
package grope

import (
"fmt"
Expand Down Expand Up @@ -34,9 +34,9 @@ type dataSourceFetcher interface {
type Dashboards []Dashboard

type Dashboard struct {
Model models.JSON
Folder string
Title string
Model models.JSON
}

func yieldDashboards(c dashboardClient, folders bool, args ...string) iter.Seq2[Dashboard, error] {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package internal
package grope

import (
"errors"
Expand Down
8 changes: 4 additions & 4 deletions internal/formatter.go → internal/grope/formatter.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
package internal
package grope

import (
"bytes"
"encoding/json"
"fmt"
"github.com/gosimple/slug"
"github.com/grafana/grafana-openapi-client-go/models"
grafanav1beta1 "github.com/grafana/grafana-operator/v5/api/v1beta1"
"github.com/grafana/grafana-operator/v5/api/v1beta1"
"gopkg.in/yaml.v3"
"io"
"iter"
Expand Down Expand Up @@ -52,7 +52,7 @@ func (f formatter) formatDashboard(w io.Writer, dashboard Dashboard) error {
}

dashboardCR := grafanaOperatorCustomResource{
APIVersion: grafanav1beta1.GroupVersion.String(),
APIVersion: v1beta1.GroupVersion.String(),
Kind: "GrafanaDashboard",
Metadata: metadata{
Name: slug.Make(dashboard.Title),
Expand Down Expand Up @@ -91,7 +91,7 @@ func (f formatter) grafanaOperatorCustomResources(dataSources []*models.DataSour
return func(yield func(grafanaOperatorCustomResource) bool) {
for _, dataSource := range dataSources {
cr := grafanaOperatorCustomResource{
APIVersion: grafanav1beta1.GroupVersion.String(),
APIVersion: v1beta1.GroupVersion.String(),
Kind: "GrafanaDataSource",
Metadata: metadata{
Name: "datasource-" + slug.Make(dataSource.Name),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ spec:
dashboards: grafana
json: |
{
"foo": "bar"
"foo": "bar",
"tags": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ spec:
dashboards: grafana
json: |
{
"foo": "bar"
"foo": "bar",
"tags": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ spec:
dashboards: local-grafana
json: |
{
"foo": "bar"
"foo": "bar",
"tags": []
}
---
apiVersion: grafana.integreatly.org/v1beta1
Expand All @@ -28,5 +29,6 @@ spec:
dashboards: local-grafana
json: |
{
"foo": "bar"
"foo": "bar",
"tags": []
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ spec:
dashboards: grafana
json: |
{
"foo": "bar"
"foo": "bar",
"tags": []
}
---
apiVersion: grafana.integreatly.org/v1beta1
Expand All @@ -28,5 +29,6 @@ spec:
dashboards: grafana
json: |
{
"foo": "bar"
"foo": "bar",
"tags": []
}
38 changes: 38 additions & 0 deletions internal/grope/testdata/testexportdashboards-tagged.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDashboard
metadata:
name: db-1
namespace: default
spec:
allowCrossNamespaceImport: true
folder: folder 1
instanceSelector:
matchLabels:
dashboards: grafana
json: |
{
"foo": "bar",
"tags": [
"grope"
]
}
---
apiVersion: grafana.integreatly.org/v1beta1
kind: GrafanaDashboard
metadata:
name: db-2
namespace: default
spec:
allowCrossNamespaceImport: true
folder: folder 2
instanceSelector:
matchLabels:
dashboards: grafana
json: |
{
"foo": "bar",
"tags": [
"grope"
]
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@ spec:
dashboards: grafana
json: |
{
"foo": "bar"
"foo": "bar",
"tags": []
}
---
apiVersion: grafana.integreatly.org/v1beta1
Expand All @@ -28,5 +29,6 @@ spec:
dashboards: grafana
json: |
{
"foo": "bar"
"foo": "bar",
"tags": []
}
Loading

0 comments on commit d35163d

Please sign in to comment.