Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: grafana import should allow rewriting cluster label #3135

Merged
merged 2 commits into from
Sep 5, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 66 additions & 3 deletions cmd/tools/grafana/grafana.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ type options struct {
svmRegex string
customizeDir string
customAllValue string
customCluster string
}

type Folder struct {
Expand Down Expand Up @@ -516,6 +517,11 @@ func importFiles(dir string, folder *Folder) {
data = addSvmRegex(data, file.Name(), opts.svmRegex)
}

// change cluster label if needed
if opts.customCluster != "" {
data = changeClusterLabel(data, opts.customCluster)
}

// labelMap is used to ensure we don't modify the query of one of the new labels we're adding
labelMap := make(map[string]string)
caser := cases.Title(language.Und)
Expand Down Expand Up @@ -587,6 +593,60 @@ func importFiles(dir string, folder *Folder) {
}
}

// This function will rewrite all panel expressions in the dashboard to use the new cluster label.
// Example:
// sum(write_data{datacenter=~"$Datacenter",cluster=~"$Cluster",svm=~"$SVM"})
// with --cluster-label=na_cluster will become
// sum(write_data{datacenter=~"$Datacenter",na_cluster=~"$Cluster",svm=~"$SVM"})
// See https://github.com/NetApp/harvest/issues/3131
func changeClusterLabel(data []byte, cluster string) []byte {
cgrinds marked this conversation as resolved.
Show resolved Hide resolved

// Change all panel expressions
VisitAllPanels(data, func(path string, _, value gjson.Result) {
value.Get("targets").ForEach(func(targetKey, target gjson.Result) bool {
expr := target.Get("expr")
if expr.Exists() {
newExpr := strings.ReplaceAll(expr.String(), "cluster", cluster)

destPath := path + ".targets." + targetKey.String() + ".expr"
data, _ = sjson.SetBytes(data, destPath, []byte(newExpr))
}

legendFormat := target.Get("legendFormat")
if legendFormat.Exists() {
newLegendFormat := strings.ReplaceAll(legendFormat.String(), "cluster", cluster)

destPath := path + ".targets." + targetKey.String() + ".legendFormat"
data, _ = sjson.SetBytes(data, destPath, []byte(newLegendFormat))
}

return true
})
})

// Change all templating variables that contain cluster
gjson.GetBytes(data, "templating.list").ForEach(func(key, value gjson.Result) bool {

// Change definition
definition := value.Get("definition")
if definition.Exists() {
newDefinition := strings.ReplaceAll(definition.String(), "cluster", cluster)
data, _ = sjson.SetBytes(data, "templating.list."+key.String()+".definition", []byte(newDefinition))
}

// Change query
query := value.Get("query.query")
if query.Exists() {
newQuery := strings.ReplaceAll(query.String(), "cluster", cluster)
data, _ = sjson.SetBytes(data, "templating.list."+key.String()+".query.query", []byte(newQuery))
}

return true
})

return data
}

func writeCustomDashboard(dashboard map[string]any, dir string, file os.DirEntry) error {
data, err := json.Marshal(dashboard)
if err != nil {
Expand Down Expand Up @@ -617,8 +677,8 @@ func formatJSON(data []byte) ([]byte, error) {
return prettyJSON.Bytes(), nil
}

// addGlobalPrefix adds the given prefix to all metric names in the
// dashboards. It assumes that metrics are in Prometheus-format.
// addGlobalPrefix adds the given prefix to all metric names in the dashboards.
// It assumes that metrics are in Prometheus format.
//
// A more reliable implementation of this feature would be, to
// add a constant prefix to all metrics, before they are pushed
Expand Down Expand Up @@ -717,7 +777,7 @@ func handlingPanels(p interface{}, prefix string) {

// addPrefixToMetricNames adds prefix to metric names in expr or leaves it
// unchanged if no metric names are identified.
// Note that this function will only work with the Prometheus-dashboards of Harvest.
// Note that this function will only work with the Prometheus dashboards of Harvest.
// It will use a number of patterns in which metrics might be used in queries.
// (E.g. a single metric, multiple metrics used in addition, etc. -- for examples
// see the test). If we change queries of our dashboards, we have to review
Expand Down Expand Up @@ -1153,6 +1213,9 @@ func addImportCustomizeFlags(commands ...*cobra.Command) {
"Modify the dashboards to add multi-select dropdowns for each variable")
cmd.PersistentFlags().BoolVar(&opts.forceImport, "force", false,
"Import even if the datasource name is not defined in Grafana")
cmd.PersistentFlags().StringVar(&opts.customCluster, "cluster-label", "",
"Rewrite all panel expressions to use the specified cluster label instead of the default 'cluster'")

_ = cmd.PersistentFlags().MarkHidden("multi")
_ = cmd.PersistentFlags().MarkHidden("force")
}
Expand Down
7 changes: 5 additions & 2 deletions cmd/tools/grafana/metrics.go
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,11 @@ func VisitAllPanels(data []byte, handle func(path string, key gjson.Result, valu

func visitPanels(data []byte, panelPath string, pathPrefix string, handle func(path string, key gjson.Result, value gjson.Result)) {
gjson.GetBytes(data, panelPath).ForEach(func(key, value gjson.Result) bool {
path := fmt.Sprintf("%s[%d]", panelPath, key.Int())
fullPath := fmt.Sprintf("%s%s", pathPrefix, path)
path := panelPath + "." + key.String()
fullPath := path
if pathPrefix != "" {
fullPath = pathPrefix + "." + path
}
handle(fullPath, key, value)
visitPanels([]byte(value.Raw), "panels", fullPath, handle)
return true
Expand Down