From 4d0e2f9eafce88efea7e562f534f3f4c8ec71bc7 Mon Sep 17 00:00:00 2001 From: Yufei Li Date: Fri, 8 Sep 2023 13:06:57 +0800 Subject: [PATCH] fix: Disable incorrect variables --- internal/grafana/addons/variables/funcs.go | 22 +++ internal/grafana/addons/variables/parser.go | 131 ++++++++++++++++++ .../grafana/addons/variables/parser_test.go | 69 +++++++++ .../grafana/addons/variables/variables.go | 41 ++---- 4 files changed, 237 insertions(+), 26 deletions(-) create mode 100644 internal/grafana/addons/variables/funcs.go create mode 100644 internal/grafana/addons/variables/parser.go create mode 100644 internal/grafana/addons/variables/parser_test.go diff --git a/internal/grafana/addons/variables/funcs.go b/internal/grafana/addons/variables/funcs.go new file mode 100644 index 0000000..e13f46c --- /dev/null +++ b/internal/grafana/addons/variables/funcs.go @@ -0,0 +1,22 @@ +package variables + +// PromQLFuncLabelNames is the label_names function in Grafana +type GrafanaFuncLabelNames struct { + MetricRegexp string +} + +// PromQLFuncLabelValues is the label_values function in Grafana +type GrafanaFuncLabelValues struct { + Metric string + Label string +} + +// PromQLFuncQueryResult is the query_result function in Grafana +type GrafanaFuncQueryResult struct { + Query string +} + +// PromQLFuncMetrics is the metrics function in Grafana +type GrafanaFuncMetrics struct { + MetricRegexp string +} diff --git a/internal/grafana/addons/variables/parser.go b/internal/grafana/addons/variables/parser.go new file mode 100644 index 0000000..0469317 --- /dev/null +++ b/internal/grafana/addons/variables/parser.go @@ -0,0 +1,131 @@ +package variables + +import ( + "fmt" + "regexp" +) + +var ( + // PromQLLabelNamesFuncName is the label_names function in Grafana + PrometheusLabelNamesRegex = regexp.MustCompile(`^label_names\(\)\s*$`) + + // PromQLLabelValuesFuncName is the label_values function in Grafana + PrometheusLabelValuesRegex = regexp.MustCompile(`^label_values\((?:(.+),\s*)?([a-zA-Z_$][a-zA-Z0-9_]*)\)\s*$`) + + // PromQLMetricsFuncName is the metrics function in Grafana + PrometheusMetricNamesRegex = regexp.MustCompile(`^metrics\((.+)\)\s*$`) + + // PromQLQueryResultFuncName is the query_result function in Grafana + PrometheusQueryResultRegex = regexp.MustCompile(`^query_result\((.+)\)\s*$`) + + // PromQLLabelNamesFuncNameWithMatch is the label_names function in Grafana + PrometheusLabelNamesRegexWithMatch = regexp.MustCompile(`^label_names\((.+)\)\s*$`) +) + +const ( + // PrometheusLabelNamesFuncName is the label_names function name in Grafana + PrometheusLabelNamesFuncName = "label_names" + + // PrometheusLabelValuesFuncName is the label_values function name in Grafana + PrometheusLabelValuesFuncName = "label_values" + + // PrometheusMetricsFuncName is the metrics function name in Grafana + PrometheusMetricNamesFuncName = "metrics" + + // PrometheusQueryResultFuncName is the query_result function name in Grafana + PrometheusQueryResultFuncName = "query_result" +) + +type GrafanaVariable struct { + Expr string + FuncName string + Func interface{} +} + +func ParseGrafanaVariable(expr string) (*GrafanaVariable, error) { + switch { + case PrometheusLabelValuesRegex.MatchString(expr): + match := PrometheusLabelValuesRegex.FindStringSubmatch(expr) + if len(match) != 3 { + return nil, fmt.Errorf("failed to get label from variable: %s", expr) + } + return &GrafanaVariable{ + Expr: expr, + FuncName: PrometheusLabelValuesFuncName, + Func: &GrafanaFuncLabelValues{ + Metric: match[1], + Label: match[2], + }, + }, nil + case PrometheusQueryResultRegex.MatchString(expr): + match := PrometheusQueryResultRegex.FindStringSubmatch(expr) + if len(match) != 2 { + return nil, fmt.Errorf("failed to get query: %s", expr) + } + return &GrafanaVariable{ + Expr: expr, + FuncName: PrometheusQueryResultFuncName, + Func: &GrafanaFuncQueryResult{ + Query: match[1], + }, + }, nil + case PrometheusMetricNamesRegex.MatchString(expr): + match := PrometheusMetricNamesRegex.FindStringSubmatch(expr) + if len(match) != 2 { + return nil, fmt.Errorf("failed to get metric: %s", expr) + } + return &GrafanaVariable{ + Expr: expr, + FuncName: PrometheusMetricNamesFuncName, + Func: &GrafanaFuncMetrics{ + MetricRegexp: match[1], + }, + }, nil + case PrometheusLabelNamesRegexWithMatch.MatchString(expr): + match := PrometheusLabelNamesRegexWithMatch.FindStringSubmatch(expr) + if len(match) != 2 { + return nil, fmt.Errorf("failed to get metric: %s", expr) + } + return &GrafanaVariable{ + Expr: expr, + FuncName: PrometheusLabelNamesFuncName, + Func: &GrafanaFuncLabelNames{ + MetricRegexp: match[1], + }, + }, nil + case PrometheusLabelNamesRegex.MatchString(expr): + return &GrafanaVariable{ + Expr: expr, + FuncName: PrometheusLabelNamesFuncName, + Func: &GrafanaFuncLabelNames{ + MetricRegexp: "", + }, + }, nil + default: + return &GrafanaVariable{ + Expr: expr, + FuncName: "", + Func: nil, + }, nil + } +} + +func toDQL(promExpr string) (string, error) { + grafanaVariable, err := ParseGrafanaVariable(promExpr) + if err != nil { + return "", fmt.Errorf("failed to parse grafana variable: %w", err) + } + + switch grafanaVariable.FuncName { + case PrometheusLabelNamesFuncName: + return "", nil + case PrometheusLabelValuesFuncName: + return "", nil + case PrometheusQueryResultFuncName: + return "", nil + case PrometheusMetricNamesFuncName: + return "", nil + default: + return "", nil + } +} diff --git a/internal/grafana/addons/variables/parser_test.go b/internal/grafana/addons/variables/parser_test.go new file mode 100644 index 0000000..ab37c4c --- /dev/null +++ b/internal/grafana/addons/variables/parser_test.go @@ -0,0 +1,69 @@ +package variables + +import ( + "fmt" + "testing" + + "github.com/stretchr/testify/assert" +) + +func TestParseGrafanaVariable(t *testing.T) { + tests := []struct { + expr string + wantErr bool + }{ + { + expr: "label_names(cpu_seconds_total)", + wantErr: true, + }, + { + expr: "label_names(cpu_seconds_.*)", + wantErr: true, + }, + { + expr: "label_values(cpu_seconds_total)", + wantErr: true, + }, + { + expr: "label_values(cpu_seconds_total, app)", + wantErr: true, + }, + { + expr: `label_values(cpu_seconds_total{namespace="$namespace"})`, + wantErr: true, + }, + { + expr: `label_values(cpu_seconds_total{namespace="$namespace"}, app)`, + wantErr: true, + }, + { + expr: "metrics(cpu_seconds_total)", + wantErr: true, + }, + { + expr: "metrics(cpu_seconds_.*)", + wantErr: true, + }, + { + expr: `query_result(cpu_seconds_total{namespace="$namespace"})`, + wantErr: true, + }, + { + expr: `cpu_seconds_total{namespace="$namespace"}`, + wantErr: true, + }, + } + + for _, tt := range tests { + dql, err := toDQL(tt.expr) + if err != nil { + if tt.wantErr { + assert.Error(t, err) + } else { + assert.NoError(t, err) + } + continue + } + fmt.Println(dql) + } +} diff --git a/internal/grafana/addons/variables/variables.go b/internal/grafana/addons/variables/variables.go index fa65972..96cd01c 100644 --- a/internal/grafana/addons/variables/variables.go +++ b/internal/grafana/addons/variables/variables.go @@ -2,29 +2,40 @@ package variables import ( "fmt" - "regexp" - "strings" "github.com/hashicorp/go-multierror" grafanaspec "github.com/GuanceCloud/guance-cli/internal/grafana/spec" + "github.com/GuanceCloud/guance-cli/internal/helpers/types" ) // BuildVariables builds variables for Guance Cloud func (addon *Addon) BuildVariables(variables []grafanaspec.VariableModel) ([]any, error) { var mErr error + vars := make([]any, 0, len(variables)) for _, variable := range variables { if variable.Type != "query" { continue } - dqlQuery, err := addon.toDQL(variable) + promExpr, err := getPromExpr(variable) + if err != nil { + mErr = multierror.Append(mErr, fmt.Errorf("failed to get prometheus expression from variable: %w", err)) + continue + } + + dqlQuery, err := toDQL(promExpr) if err != nil { mErr = multierror.Append(mErr, fmt.Errorf("failed to get label from variable: %w", err)) continue } + name := types.StringValue(variable.Label) + if name == "" { + name = variable.Name + } + vars = append(vars, map[string]any{ "code": variable.Name, "datasource": "dataflux", @@ -41,7 +52,7 @@ func (addon *Addon) BuildVariables(variables []grafanaspec.VariableModel) ([]any }, "hide": 0, "isHiddenAsterisk": 0, - "name": variable.Label, + "name": name, "seq": 2, "type": "QUERY", "valueSort": "asc", @@ -53,28 +64,6 @@ func (addon *Addon) BuildVariables(variables []grafanaspec.VariableModel) ([]any return vars, nil } -var labelFuncPattern = regexp.MustCompile(`label_values\((.+),?\s*(.*)\)`) - -func (addon *Addon) toDQL(variable grafanaspec.VariableModel) (string, error) { - queryString, err := getPromExpr(variable) - if err != nil { - return "", fmt.Errorf("failed to get prometheus expression: %w", err) - } - - switch { - case strings.HasPrefix(queryString, "label_values("): - match := labelFuncPattern.FindStringSubmatch(queryString) - if len(match) != 3 { - return "", fmt.Errorf("failed to get label from variable: %s", variable.Name) - } - return fmt.Sprintf("SHOW_TAG_VALUE(from=['%s'], keyin=['%s'])", addon.Measurement, match[2]), nil - case strings.HasPrefix(queryString, "query_result("): - return "", nil - default: - return "", fmt.Errorf("failed to get label from variable: %s", variable.Name) - } -} - func getPromExpr(variable grafanaspec.VariableModel) (string, error) { if variable.Query == nil { return "", fmt.Errorf("query %s is empty", variable.Name)