Be more tolerant to invalid JSON when parsing dashboards for indexing (#114040)
* Tags can only be string array. * Be more lenient when parsing dashboard. Parse what we can, don't error out easily. * Check element before parsing, log unexpected types.
This commit is contained in:
@@ -1,16 +1,16 @@
|
||||
package dashboard
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
func logf(format string, a ...any) {
|
||||
//fmt.Printf(format, a...)
|
||||
}
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
)
|
||||
|
||||
type templateVariable struct {
|
||||
current struct {
|
||||
@@ -48,7 +48,7 @@ func (d *datasourceVariableLookup) getDsRefsByTemplateVariableValue(value string
|
||||
case "No data sources found":
|
||||
return []DataSourceRef{}
|
||||
default:
|
||||
// some variables use `ds.name` rather `ds.uid`
|
||||
// some variables use `ds.name` rather than `ds.uid`
|
||||
if ref := d.dsLookup.ByRef(&DataSourceRef{
|
||||
UID: value,
|
||||
}); ref != nil {
|
||||
@@ -114,45 +114,63 @@ func newDatasourceVariableLookup(dsLookup DatasourceLookup) *datasourceVariableL
|
||||
|
||||
// ReadDashboard will take a byte stream and return dashboard info
|
||||
func ReadDashboard(stream io.Reader, lookup DatasourceLookup) (*DashboardSummaryInfo, error) {
|
||||
return ReadDashboardWithLogContext(stream, lookup, nil)
|
||||
}
|
||||
|
||||
func ReadDashboardWithLogContext(stream io.Reader, lookup DatasourceLookup, logContext map[string]any) (*DashboardSummaryInfo, error) {
|
||||
iter := jsoniter.Parse(jsoniter.ConfigDefault, stream, 1024)
|
||||
return readDashboardIter(iter, lookup)
|
||||
return readDashboardIter("$", iter, lookup, logContext)
|
||||
}
|
||||
|
||||
// nolint:gocyclo
|
||||
func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*DashboardSummaryInfo, error) {
|
||||
func readDashboardIter(jsonPath string, iter *jsoniter.Iterator, lookup DatasourceLookup, lc map[string]any) (*DashboardSummaryInfo, error) {
|
||||
dash := &DashboardSummaryInfo{}
|
||||
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath, lc, jsoniter.ObjectValue) {
|
||||
return dash, errors.New("expected JSON object at " + jsonPath)
|
||||
}
|
||||
|
||||
datasourceVariablesLookup := newDatasourceVariableLookup(lookup)
|
||||
|
||||
for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
|
||||
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
|
||||
// Skip null values so we don't need special int handling
|
||||
if iter.WhatIsNext() == jsoniter.NilValue {
|
||||
iter.Skip()
|
||||
continue
|
||||
}
|
||||
|
||||
switch l1Field {
|
||||
switch field {
|
||||
// k8s metadata wrappers (skip)
|
||||
case "metadata", "kind", "apiVersion":
|
||||
_ = iter.Read()
|
||||
iter.Skip()
|
||||
|
||||
// recursively read the spec as dashboard json
|
||||
case "spec":
|
||||
return readDashboardIter(iter, lookup)
|
||||
return readDashboardIter(jsonPath+".spec", iter, lookup, lc)
|
||||
|
||||
case "id":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".id", lc, jsoniter.NumberValue) {
|
||||
continue
|
||||
}
|
||||
dash.ID = iter.ReadInt64()
|
||||
|
||||
case "uid":
|
||||
iter.ReadString()
|
||||
|
||||
case "title":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".title", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
dash.Title = iter.ReadString()
|
||||
|
||||
case "description":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".description", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
dash.Description = iter.ReadString()
|
||||
|
||||
case "schemaVersion":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".schemaVersion", lc, jsoniter.NumberValue, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch iter.WhatIsNext() {
|
||||
case jsoniter.NumberValue:
|
||||
dash.SchemaVersion = iter.ReadInt64()
|
||||
@@ -164,10 +182,18 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
|
||||
default:
|
||||
iter.Skip()
|
||||
}
|
||||
|
||||
case "timezone":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".timezone", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
dash.TimeZone = iter.ReadString()
|
||||
|
||||
case "editable":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".editable", lc, jsoniter.StringValue, jsoniter.BoolValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch iter.WhatIsNext() {
|
||||
case jsoniter.BoolValue:
|
||||
dash.ReadOnly = !iter.ReadBool()
|
||||
@@ -178,25 +204,42 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
|
||||
}
|
||||
|
||||
case "refresh":
|
||||
nxt := iter.WhatIsNext()
|
||||
if nxt == jsoniter.StringValue {
|
||||
dash.Refresh = iter.ReadString()
|
||||
} else {
|
||||
iter.Skip()
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".refresh", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
dash.Refresh = iter.ReadString()
|
||||
|
||||
case "tags":
|
||||
for iter.ReadArray() {
|
||||
tagsPath := jsonPath + ".tags"
|
||||
|
||||
// Only support string array tags. Ignore everything else.
|
||||
if !checkAndSkipUnexpectedElement(iter, tagsPath, lc, jsoniter.ArrayValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
for ix := 0; iter.ReadArray(); ix++ {
|
||||
if !checkAndSkipUnexpectedElement(iter, fmt.Sprintf("%s[%d]", tagsPath, ix), lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
dash.Tags = append(dash.Tags, iter.ReadString())
|
||||
}
|
||||
|
||||
case "links":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".links", lc, jsoniter.ArrayValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
for iter.ReadArray() {
|
||||
iter.Skip()
|
||||
dash.LinkCount++
|
||||
}
|
||||
|
||||
case "time":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".time", lc, jsoniter.ObjectValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
obj, ok := iter.Read().(map[string]any)
|
||||
if ok {
|
||||
if timeFrom, ok := obj["from"].(string); ok {
|
||||
@@ -206,61 +249,66 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
|
||||
dash.TimeTo = timeTo
|
||||
}
|
||||
}
|
||||
|
||||
case "panels":
|
||||
for iter.ReadArray() {
|
||||
dash.Panels = append(dash.Panels, readpanelInfo(iter, lookup))
|
||||
panelsPath := jsonPath + ".panels"
|
||||
if !checkAndSkipUnexpectedElement(iter, panelsPath, lc, jsoniter.ArrayValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
case "rows":
|
||||
for iter.ReadArray() {
|
||||
v := iter.Read()
|
||||
logf("[DASHBOARD.ROW???] id=%s // %v\n", dash.ID, v)
|
||||
}
|
||||
|
||||
case "annotations":
|
||||
switch iter.WhatIsNext() {
|
||||
case jsoniter.ArrayValue:
|
||||
// dashboards v2 is an array
|
||||
for iter.ReadArray() {
|
||||
v := iter.Read()
|
||||
logf("[dash.anno] %v\n", v)
|
||||
for ix := 0; iter.ReadArray(); ix++ {
|
||||
p, ok := readpanelInfo(iter, lookup, fmt.Sprintf("%s[%d]", panelsPath, ix), lc)
|
||||
if ok {
|
||||
dash.Panels = append(dash.Panels, p)
|
||||
}
|
||||
case jsoniter.ObjectValue:
|
||||
// dashboards v0/v1 are an object
|
||||
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
|
||||
if sub == "list" {
|
||||
for iter.ReadArray() {
|
||||
v := iter.Read()
|
||||
logf("[dash.anno] %v\n", v)
|
||||
}
|
||||
} else {
|
||||
iter.Skip()
|
||||
}
|
||||
}
|
||||
default:
|
||||
iter.Skip()
|
||||
}
|
||||
|
||||
case "templating":
|
||||
templatingPath := jsonPath + ".templating"
|
||||
if !checkAndSkipUnexpectedElement(iter, templatingPath, lc, jsoniter.ObjectValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
|
||||
if sub == "list" {
|
||||
for iter.ReadArray() {
|
||||
templateVariable := templateVariable{}
|
||||
templatingListPath := templatingPath + ".list"
|
||||
if !checkAndSkipUnexpectedElement(iter, templatingListPath, lc, jsoniter.ArrayValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
for ix := 0; iter.ReadArray(); ix++ {
|
||||
tv := templateVariable{}
|
||||
|
||||
templatingListElementPath := fmt.Sprintf("%s[%d]", templatingListPath, ix)
|
||||
if !checkAndSkipUnexpectedElement(iter, templatingListElementPath, lc, jsoniter.ObjectValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
for k := iter.ReadObject(); k != ""; k = iter.ReadObject() {
|
||||
switch k {
|
||||
case "name":
|
||||
if !checkAndSkipUnexpectedElement(iter, templatingListElementPath+".name", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
name := iter.ReadString()
|
||||
dash.TemplateVars = append(dash.TemplateVars, name)
|
||||
templateVariable.name = name
|
||||
tv.name = name
|
||||
case "type":
|
||||
templateVariable.variableType = iter.ReadString()
|
||||
if !checkAndSkipUnexpectedElement(iter, templatingListElementPath+".type", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
tv.variableType = iter.ReadString()
|
||||
case "query":
|
||||
templateVariable.query = iter.Read()
|
||||
tv.query = iter.Read()
|
||||
case "current":
|
||||
if !checkAndSkipUnexpectedElement(iter, templatingListElementPath+".current", lc, jsoniter.ObjectValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
for c := iter.ReadObject(); c != ""; c = iter.ReadObject() {
|
||||
if c == "value" {
|
||||
templateVariable.current.value = iter.Read()
|
||||
tv.current.value = iter.Read()
|
||||
} else {
|
||||
iter.Skip()
|
||||
}
|
||||
@@ -270,8 +318,8 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
|
||||
}
|
||||
}
|
||||
|
||||
if templateVariable.variableType == "datasource" {
|
||||
datasourceVariablesLookup.add(templateVariable)
|
||||
if tv.variableType == "datasource" {
|
||||
datasourceVariablesLookup.add(tv)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -279,17 +327,9 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
|
||||
}
|
||||
}
|
||||
|
||||
// Ignore these properties
|
||||
case "timepicker":
|
||||
fallthrough
|
||||
case "version":
|
||||
fallthrough
|
||||
case "iteration":
|
||||
iter.Skip()
|
||||
|
||||
// Ignore everything else
|
||||
default:
|
||||
v := iter.Read()
|
||||
logf("[DASHBOARD] support key: %s / %v\n", l1Field, v)
|
||||
iter.Skip()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,6 +351,64 @@ func readDashboardIter(iter *jsoniter.Iterator, lookup DatasourceLookup) (*Dashb
|
||||
return dash, iter.Error
|
||||
}
|
||||
|
||||
var logger = log.New("services.store.kind.dashboard")
|
||||
|
||||
// checkAndSkipUnexpectedElement verifies if the next JSON element matches any allowed value types for the specified JSON path.
|
||||
// If the type matches, it returns true, otherwise it skips the element, logs an error, and returns false.
|
||||
func checkAndSkipUnexpectedElement(iter *jsoniter.Iterator, jsonPath string, logContext map[string]any, allowedValues ...jsoniter.ValueType) bool {
|
||||
next := iter.WhatIsNext()
|
||||
for _, a := range allowedValues {
|
||||
if next == a {
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Skip unexpected element.
|
||||
iter.Skip()
|
||||
|
||||
// Prepare log message.
|
||||
params := []any{
|
||||
"jsonPath", jsonPath,
|
||||
"got", valueTypesToString(next),
|
||||
"expected", valueTypesToString(allowedValues...),
|
||||
}
|
||||
|
||||
// Map iteration is random, so a log message may look different each time if there are multiple entries in the map. That's fine.
|
||||
for k, v := range logContext {
|
||||
params = append(params, k, v)
|
||||
}
|
||||
|
||||
logger.Error("Unexpected element in Dashboard JSON", params...)
|
||||
return false
|
||||
}
|
||||
|
||||
func valueTypesToString(allowedValues ...jsoniter.ValueType) string {
|
||||
expected := strings.Builder{}
|
||||
for ix, a := range allowedValues {
|
||||
if ix > 0 {
|
||||
expected.WriteString(", ")
|
||||
}
|
||||
|
||||
switch a {
|
||||
case jsoniter.NilValue:
|
||||
expected.WriteString("null")
|
||||
case jsoniter.StringValue:
|
||||
expected.WriteString("string")
|
||||
case jsoniter.NumberValue:
|
||||
expected.WriteString("number")
|
||||
case jsoniter.BoolValue:
|
||||
expected.WriteString("bool")
|
||||
case jsoniter.ArrayValue:
|
||||
expected.WriteString("array")
|
||||
case jsoniter.ObjectValue:
|
||||
expected.WriteString("object")
|
||||
default:
|
||||
expected.WriteString(fmt.Sprintf("unknown: %d", a))
|
||||
}
|
||||
}
|
||||
return expected.String()
|
||||
}
|
||||
|
||||
func panelRequiresDatasource(panel PanelSummaryInfo) bool {
|
||||
return panel.Type != "row"
|
||||
}
|
||||
@@ -399,16 +497,20 @@ func findDatasourceRefsForVariables(dsVariableRefs []DataSourceRef, datasourceVa
|
||||
return referencedDs
|
||||
}
|
||||
|
||||
// will always return strings for now
|
||||
func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelSummaryInfo {
|
||||
// nolint:gocyclo
|
||||
func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup, jsonPath string, lc map[string]any) (PanelSummaryInfo, bool) {
|
||||
panel := PanelSummaryInfo{}
|
||||
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath, lc, jsoniter.ObjectValue) {
|
||||
return panel, false
|
||||
}
|
||||
|
||||
targets := newTargetInfo(lookup)
|
||||
|
||||
for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
|
||||
for field := iter.ReadObject(); field != ""; field = iter.ReadObject() {
|
||||
if iter.WhatIsNext() == jsoniter.NilValue {
|
||||
if l1Field == "datasource" {
|
||||
targets.addDatasource(iter)
|
||||
if field == "datasource" {
|
||||
targets.addDatasource(iter, jsonPath+".datasource", lc)
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -417,23 +519,42 @@ func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelSummar
|
||||
continue
|
||||
}
|
||||
|
||||
switch l1Field {
|
||||
switch field {
|
||||
case "id":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".id", lc, jsoniter.NumberValue) {
|
||||
continue
|
||||
}
|
||||
panel.ID = iter.ReadInt64()
|
||||
|
||||
case "type":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".type", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
panel.Type = iter.ReadString()
|
||||
|
||||
case "title":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".title", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
panel.Title = iter.ReadString()
|
||||
|
||||
case "description":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".description", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
panel.Description = iter.ReadString()
|
||||
|
||||
case "pluginVersion":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".pluginVersion", lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
panel.PluginVersion = iter.ReadString() // since 7x (the saved version for the plugin model)
|
||||
|
||||
case "libraryPanel":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".libraryPanel", lc, jsoniter.ObjectValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
var v map[string]interface{}
|
||||
iter.ReadVal(&v)
|
||||
if uid, ok := v["uid"]; ok {
|
||||
@@ -443,26 +564,42 @@ func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelSummar
|
||||
}
|
||||
|
||||
case "datasource":
|
||||
targets.addDatasource(iter)
|
||||
targets.addDatasource(iter, jsonPath+".datasource", lc)
|
||||
|
||||
case "targets":
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".targets", lc, jsoniter.ArrayValue, jsoniter.ObjectValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
switch iter.WhatIsNext() {
|
||||
case jsoniter.ArrayValue:
|
||||
for iter.ReadArray() {
|
||||
targets.addTarget(iter)
|
||||
for ix := 0; iter.ReadArray(); ix++ {
|
||||
targets.addTarget(iter, fmt.Sprintf("%s.targets[%d]", jsonPath, ix), lc)
|
||||
}
|
||||
case jsoniter.ObjectValue:
|
||||
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
|
||||
targets.addTarget(iter)
|
||||
for fn := iter.ReadObject(); fn != ""; fn = iter.ReadObject() {
|
||||
targets.addTarget(iter, jsonPath+".targets."+fn, lc)
|
||||
}
|
||||
default:
|
||||
iter.Skip()
|
||||
}
|
||||
|
||||
case "transformations":
|
||||
for iter.ReadArray() {
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".transformations", lc, jsoniter.ArrayValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
for ix := 0; iter.ReadArray(); ix++ {
|
||||
if !checkAndSkipUnexpectedElement(iter, fmt.Sprintf("%s.transformations[%d]", jsonPath, ix), lc, jsoniter.ObjectValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
for sub := iter.ReadObject(); sub != ""; sub = iter.ReadObject() {
|
||||
if sub == "id" {
|
||||
if !checkAndSkipUnexpectedElement(iter, fmt.Sprintf("%s.transformations[%d].id", jsonPath, ix), lc, jsoniter.StringValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
panel.Transformer = append(panel.Transformer, iter.ReadString())
|
||||
} else {
|
||||
iter.Skip()
|
||||
@@ -472,26 +609,26 @@ func readpanelInfo(iter *jsoniter.Iterator, lookup DatasourceLookup) PanelSummar
|
||||
|
||||
// Rows have nested panels
|
||||
case "panels":
|
||||
for iter.ReadArray() {
|
||||
panel.Collapsed = append(panel.Collapsed, readpanelInfo(iter, lookup))
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath+".panels", lc, jsoniter.ArrayValue) {
|
||||
continue
|
||||
}
|
||||
|
||||
case "options":
|
||||
fallthrough
|
||||
for ix := 0; iter.ReadArray(); ix++ {
|
||||
p, ok := readpanelInfo(iter, lookup, fmt.Sprintf("%s.panels[%d]", jsonPath, ix), lc)
|
||||
if ok {
|
||||
panel.Collapsed = append(panel.Collapsed, p)
|
||||
}
|
||||
}
|
||||
|
||||
case "gridPos":
|
||||
fallthrough
|
||||
|
||||
case "fieldConfig":
|
||||
case "options", "gridPos", "fieldConfig":
|
||||
iter.Skip()
|
||||
|
||||
default:
|
||||
v := iter.Read()
|
||||
logf("[PANEL] support key: %s / %v\n", l1Field, v)
|
||||
iter.Skip()
|
||||
}
|
||||
}
|
||||
|
||||
panel.Datasource = targets.GetDatasourceInfo()
|
||||
|
||||
return panel
|
||||
return panel, true
|
||||
}
|
||||
|
||||
@@ -55,6 +55,7 @@ func dsLookupForTests() DatasourceLookup {
|
||||
|
||||
func TestReadDashboard(t *testing.T) {
|
||||
inputs := []string{
|
||||
"absolute-garbage",
|
||||
"check-string-datasource-id",
|
||||
"all-panels",
|
||||
"panel-graph/graph-shared-tooltips",
|
||||
@@ -73,6 +74,7 @@ func TestReadDashboard(t *testing.T) {
|
||||
"panel-with-library-panel-field",
|
||||
"k8s-wrapper",
|
||||
"k8s-wrapper-editable-string",
|
||||
"k8s-wrapper-tags-string",
|
||||
}
|
||||
|
||||
devdash := "../../../../../devenv/dev-dashboards/"
|
||||
|
||||
@@ -27,7 +27,11 @@ func (s *targetInfo) GetDatasourceInfo() []DataSourceRef {
|
||||
}
|
||||
|
||||
// the node will either be string (name|uid) OR ref
|
||||
func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) {
|
||||
func (s *targetInfo) addDatasource(iter *jsoniter.Iterator, jsonPath string, lc map[string]any) {
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath, lc, jsoniter.StringValue, jsoniter.NilValue, jsoniter.ObjectValue) {
|
||||
return
|
||||
}
|
||||
|
||||
switch iter.WhatIsNext() {
|
||||
case jsoniter.StringValue:
|
||||
key := iter.ReadString()
|
||||
@@ -55,8 +59,7 @@ func (s *targetInfo) addDatasource(iter *jsoniter.Iterator) {
|
||||
}
|
||||
|
||||
default:
|
||||
v := iter.Read()
|
||||
logf("[Panel.datasource.unknown] %v\n", v)
|
||||
iter.Skip()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,18 +69,21 @@ func (s *targetInfo) addRef(ref *DataSourceRef) {
|
||||
}
|
||||
}
|
||||
|
||||
func (s *targetInfo) addTarget(iter *jsoniter.Iterator) {
|
||||
for l1Field := iter.ReadObject(); l1Field != ""; l1Field = iter.ReadObject() {
|
||||
switch l1Field {
|
||||
func (s *targetInfo) addTarget(iter *jsoniter.Iterator, jsonPath string, lc map[string]any) {
|
||||
if !checkAndSkipUnexpectedElement(iter, jsonPath, lc, jsoniter.ObjectValue) {
|
||||
return
|
||||
}
|
||||
|
||||
for f := iter.ReadObject(); f != ""; f = iter.ReadObject() {
|
||||
switch f {
|
||||
case "datasource":
|
||||
s.addDatasource(iter)
|
||||
s.addDatasource(iter, jsonPath+".datasource", lc)
|
||||
|
||||
case "refId":
|
||||
iter.Skip()
|
||||
|
||||
default:
|
||||
v := iter.Read()
|
||||
logf("[Panel.TARGET] %s=%v\n", l1Field, v)
|
||||
iter.Skip()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"title": "adfbg6f",
|
||||
"tags": null,
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "green pie",
|
||||
"libraryPanel": "a7975b7a-fb53-4ab7-951d-15810953b54f",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 0,
|
||||
"title": "",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"title": "",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"schemaVersion": 0,
|
||||
"linkCount": 4,
|
||||
"timeFrom": "",
|
||||
"timeTo": "",
|
||||
"timezone": ""
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
{
|
||||
"metadata": 123,
|
||||
"spec": {
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": "true",
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": "not a number",
|
||||
"links": [
|
||||
"http://12345",
|
||||
{
|
||||
"object": "goes, here"
|
||||
},
|
||||
null,
|
||||
true
|
||||
],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"libraryPanel": {
|
||||
"name": "green pie",
|
||||
"uid": "a7975b7a-fb53-4ab7-951d-15810953b54f"
|
||||
},
|
||||
"title": "green pie"
|
||||
},
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": "not a number",
|
||||
"title": true
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": null
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": {
|
||||
"a": 123
|
||||
}
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": null,
|
||||
"tags": 1234,
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": false,
|
||||
"timepicker": {},
|
||||
"timezone": 3.141592653589793,
|
||||
"title": "adfbg6f",
|
||||
"uid": ["aaa", "bbb", "cc"],
|
||||
"description": [null, true],
|
||||
"version": [],
|
||||
"weekStart": false
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
{
|
||||
"id": 141,
|
||||
"title": "pppp",
|
||||
"tags": null,
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
],
|
||||
"panels": [
|
||||
{
|
||||
"id": 1,
|
||||
"title": "green pie",
|
||||
"libraryPanel": "a7975b7a-fb53-4ab7-951d-15810953b54f",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"title": "green pie",
|
||||
"libraryPanel": "e1d5f519-dabd-47c6-9ad7-83d181ce1cee",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"title": "",
|
||||
"type": "barchart",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"title": "",
|
||||
"type": "graph",
|
||||
"datasource": [
|
||||
{
|
||||
"uid": "default.uid",
|
||||
"type": "default.type"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"title": "collapsed row",
|
||||
"type": "row",
|
||||
"collapsed": [
|
||||
{
|
||||
"id": 42,
|
||||
"title": "blue pie",
|
||||
"libraryPanel": "l3d2s634-fdgf-75u4-3fg3-67j966ii7jur"
|
||||
}
|
||||
]
|
||||
}
|
||||
],
|
||||
"schemaVersion": 38,
|
||||
"linkCount": 0,
|
||||
"timeFrom": "now-6h",
|
||||
"timeTo": "now",
|
||||
"timezone": ""
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
{
|
||||
"kind": "Dashboard",
|
||||
"apiVersion": "dashboard.grafana.app/v0alpha1",
|
||||
"metadata": {
|
||||
"name": "adfbg6f",
|
||||
"namespace": "default",
|
||||
"uid": "b396894e-56bf-4a01-837b-64157912ca00",
|
||||
"creationTimestamp": "2024-10-30T18:30:54Z",
|
||||
"annotations": {
|
||||
"grafana.app/createdBy": "user:be2g71ke8yoe8b",
|
||||
"grafana.app/originHash": "Grafana v9.2.0 (NA)",
|
||||
"grafana.app/originName": "UI",
|
||||
"grafana.app/originPath": "/dashboard/new"
|
||||
}
|
||||
},
|
||||
"spec": {
|
||||
"annotations": {
|
||||
"list": [
|
||||
{
|
||||
"builtIn": 1,
|
||||
"datasource": {
|
||||
"type": "grafana",
|
||||
"uid": "-- Grafana --"
|
||||
},
|
||||
"enable": true,
|
||||
"hide": true,
|
||||
"iconColor": "rgba(0, 211, 255, 1)",
|
||||
"name": "Annotations & Alerts",
|
||||
"type": "dashboard"
|
||||
}
|
||||
]
|
||||
},
|
||||
"editable": true,
|
||||
"fiscalYearStartMonth": 0,
|
||||
"graphTooltip": 0,
|
||||
"id": 141,
|
||||
"links": [],
|
||||
"liveNow": false,
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 0,
|
||||
"y": 0
|
||||
},
|
||||
"id": 1,
|
||||
"libraryPanel": {
|
||||
"name": "green pie",
|
||||
"uid": "a7975b7a-fb53-4ab7-951d-15810953b54f"
|
||||
},
|
||||
"title": "green pie"
|
||||
},
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 2,
|
||||
"libraryPanel": {
|
||||
"name": "red pie",
|
||||
"uid": "e1d5f519-dabd-47c6-9ad7-83d181ce1cee"
|
||||
},
|
||||
"title": "green pie"
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"type": "barchart"
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"type": "graph"
|
||||
},
|
||||
{
|
||||
"collapsed": true,
|
||||
"gridPos": {
|
||||
"h": 1,
|
||||
"w": 24,
|
||||
"x": 0,
|
||||
"y": 9
|
||||
},
|
||||
"id": 3,
|
||||
"panels": [
|
||||
{
|
||||
"gridPos": {
|
||||
"h": 8,
|
||||
"w": 12,
|
||||
"x": 12,
|
||||
"y": 0
|
||||
},
|
||||
"id": 42,
|
||||
"libraryPanel": {
|
||||
"name": "blue pie",
|
||||
"uid": "l3d2s634-fdgf-75u4-3fg3-67j966ii7jur"
|
||||
},
|
||||
"title": "blue pie"
|
||||
}
|
||||
],
|
||||
"title": "collapsed row",
|
||||
"type": "row"
|
||||
}
|
||||
],
|
||||
"refresh": "",
|
||||
"schemaVersion": 38,
|
||||
"tags": "tag1,tag2, tag3,,",
|
||||
"templating": {
|
||||
"list": []
|
||||
},
|
||||
"time": {
|
||||
"from": "now-6h",
|
||||
"to": "now"
|
||||
},
|
||||
"timepicker": {},
|
||||
"timezone": "",
|
||||
"title": "pppp",
|
||||
"uid": "adfbg6f",
|
||||
"version": 3,
|
||||
"weekStart": ""
|
||||
}
|
||||
}
|
||||
@@ -261,7 +261,10 @@ func (s *DashboardDocumentBuilder) BuildDocument(ctx context.Context, key *resou
|
||||
value = rsp.Value
|
||||
}
|
||||
|
||||
summary, err := dashboard.ReadDashboard(bytes.NewReader(value), s.DatasourceLookup)
|
||||
summary, err := dashboard.ReadDashboardWithLogContext(bytes.NewReader(value), s.DatasourceLookup, map[string]any{
|
||||
"document": fmt.Sprintf("%s/%s/%s/%s", key.GetNamespace(), key.GetGroup(), key.GetResource(), key.GetName()),
|
||||
"rv": rv,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user