diff --git a/go.mod b/go.mod index dd67af4dcfa..1db9c8e545b 100644 --- a/go.mod +++ b/go.mod @@ -103,7 +103,7 @@ require ( go.opentelemetry.io/otel/trace v1.6.3 golang.org/x/crypto v0.0.0-20220331220935-ae2d96664a29 golang.org/x/exp v0.0.0-20210220032938-85be41e4509f - golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 // indirect + golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 // indirect golang.org/x/oauth2 v0.0.0-20220309155454-6242fa91716a golang.org/x/sync v0.0.0-20210220032951-036812b2e83c golang.org/x/time v0.0.0-20220224211638-0e9765cccd65 @@ -153,7 +153,7 @@ require ( github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91 // indirect github.com/docker/go-units v0.4.0 // indirect github.com/edsrzf/mmap-go v1.0.0 // indirect - github.com/emicklei/proto v1.6.15 // indirect + github.com/emicklei/proto v1.10.0 // indirect github.com/felixge/httpsnoop v1.0.2 // indirect github.com/go-kit/log v0.1.0 github.com/go-logfmt/logfmt v0.5.1 // indirect @@ -211,7 +211,7 @@ require ( github.com/prometheus/exporter-toolkit v0.7.0 // indirect github.com/prometheus/node_exporter v1.0.0-rc.0.0.20200428091818-01054558c289 // indirect github.com/prometheus/procfs v0.7.3 // indirect - github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc // indirect + github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b // indirect github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be // indirect github.com/rs/cors v1.8.2 // indirect github.com/sean-/seed v0.0.0-20170313163322-e2103e2c3529 // indirect @@ -247,7 +247,7 @@ require ( github.com/blugelabs/bluge v0.1.9 github.com/golang-migrate/migrate/v4 v4.7.0 github.com/grafana/dskit v0.0.0-20211011144203-3a88ec0b675f - github.com/grafana/thema v0.0.0-20220413232647-fc54c169b508 + github.com/grafana/thema v0.0.0-20220427204245-a557e8970249 go.etcd.io/etcd v3.3.25+incompatible go.opentelemetry.io/contrib/propagators/jaeger v1.6.0 go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.6.3 @@ -298,6 +298,7 @@ require ( github.com/imdario/mergo v0.3.12 // indirect github.com/klauspost/compress v1.15.1 // indirect github.com/kylelemons/godebug v1.1.0 // indirect + github.com/mitchellh/go-wordwrap v1.0.1 // indirect github.com/mschoch/smat v0.2.0 // indirect github.com/opencontainers/image-spec v1.0.2 // indirect github.com/pierrec/lz4/v4 v4.1.8 // indirect diff --git a/go.sum b/go.sum index a7ce787fec5..8256422b9ab 100644 --- a/go.sum +++ b/go.sum @@ -883,6 +883,8 @@ github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb github.com/emicklei/go-restful v2.9.5+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= github.com/emicklei/proto v1.6.15 h1:XbpwxmuOPrdES97FrSfpyy67SSCV/wBIKXqgJzh6hNw= github.com/emicklei/proto v1.6.15/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= +github.com/emicklei/proto v1.10.0 h1:pDGyFRVV5RvV+nkBK9iy3q67FBy9Xa7vwrOTE+g5aGw= +github.com/emicklei/proto v1.10.0/go.mod h1:rn1FgRS/FANiZdD2djyH7TMA9jdRDcYQ9IEN9yvjX0A= github.com/envoyproxy/go-control-plane v0.6.9/go.mod h1:SBwIajubJHhxtWwsL9s8ss4safvEdbitLhGGK48rN6g= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -1461,6 +1463,8 @@ github.com/grafana/saml v0.0.0-20211007135653-aed1b2edd86b/go.mod h1:q83kyQoMD0v github.com/grafana/sqlds/v2 v2.3.2/go.mod h1:34uyqPBWsEvg4V/xxh6V4uIqwu1qLfOfsmScll/ukrk= github.com/grafana/thema v0.0.0-20220413232647-fc54c169b508 h1:6k0scTj6kRDjn/qLigsLc9OTiMMaWkRUxjKBFowWEWg= github.com/grafana/thema v0.0.0-20220413232647-fc54c169b508/go.mod h1:KuqTKX9lfM87uu9vt9DS/q+REqSrAm2xYMnBBvlmevA= +github.com/grafana/thema v0.0.0-20220427204245-a557e8970249 h1:PLB1iSjrosHU5MN3eJ2tSvjXX9zkek7gLeBF/L/3oFo= +github.com/grafana/thema v0.0.0-20220427204245-a557e8970249/go.mod h1:KuqTKX9lfM87uu9vt9DS/q+REqSrAm2xYMnBBvlmevA= 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-middleware v1.0.1-0.20190118093823-f849b5445de4/go.mod h1:FiyG127CGDf3tlThmgyCl78X/SZQqEOJBCDaAfeWzPs= @@ -2023,6 +2027,8 @@ github.com/mitchellh/go-testing-interface v1.0.0/go.mod h1:kRemZodwjscx+RGhAo8eI github.com/mitchellh/go-testing-interface v1.14.0 h1:/x0XQ6h+3U3nAyk1yx+bHPURrKa9sVVvYbuqZ7pIAtI= github.com/mitchellh/go-testing-interface v1.14.0/go.mod h1:gfgS7OtZj6MA4U1UrDRp04twqAjfvlZyCfX3sDjEym8= github.com/mitchellh/go-wordwrap v1.0.0/go.mod h1:ZXFpozHsX6DPmq2I0TCekCxypsnAUbP2oI0UX1GXzOo= +github.com/mitchellh/go-wordwrap v1.0.1 h1:TLuKupo69TCn6TQSyGxwI1EblZZEsQ0vMlAFQflz0v0= +github.com/mitchellh/go-wordwrap v1.0.1/go.mod h1:R62XHJLzvMFRBbcrT7m7WgmE1eOyTSsCt+hzestvNj0= github.com/mitchellh/gox v0.4.0/go.mod h1:Sd9lOJ0+aimLBi73mGofS1ycjY8lL3uZM3JPS42BGNg= github.com/mitchellh/hashstructure v0.0.0-20170609045927-2bca23e0e452/go.mod h1:QjSHrPWS+BGUVBYkbTZWEnOh3G1DutKwClXU/ABz6AQ= github.com/mitchellh/iochan v1.0.0/go.mod h1:JwYml1nuB7xOzsp52dPpHFffvOCDupsG0QubkSMEySY= @@ -2390,6 +2396,8 @@ github.com/prometheus/statsd_exporter v0.21.0/go.mod h1:rbT83sZq2V+p73lHhPZfMc3M github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU= github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc h1:gSVONBi2HWMFXCa9jFdYvYk7IwW/mTLxWOF7rXS4LO0= github.com/protocolbuffers/txtpbfmt v0.0.0-20201118171849-f6a6b3f636fc/go.mod h1:KbKfKPy2I6ecOIGA9apfheFv14+P3RSmmQvshofQyMY= +github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b h1:zd/2RNzIRkoGGMjE+YIsZ85CnDIz672JK2F3Zl4vux4= +github.com/protocolbuffers/txtpbfmt v0.0.0-20220428173112-74888fd59c2b/go.mod h1:KjY0wibdYKc4DYkerHSbguaf3JeIPGhNJBp2BNiFH78= github.com/rafaeljusto/redigomock v0.0.0-20190202135759-257e089e14a1/go.mod h1:JaY6n2sDr+z2WTsXkOmNRUfDy6FN0L6Nk7x06ndm4tY= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be h1:ta7tUOvsPHVHGom5hKW5VXNc2xZIkfCKP8iaqOyYtUQ= github.com/rainycape/unidecode v0.0.0-20150907023854-cb7f23ec59be/go.mod h1:MIDFMn7db1kT65GmV94GzpX9Qdi7N/pQlwb+AN8wh+Q= @@ -2602,10 +2610,14 @@ github.com/thanos-io/thanos v0.13.1-0.20210224074000-659446cab117/go.mod h1:kdqF github.com/thanos-io/thanos v0.13.1-0.20210226164558-03dace0a1aa1/go.mod h1:gMCy4oCteKTT7VuXVvXLTPGzzjovX1VPE5p+HgL1hyU= github.com/thanos-io/thanos v0.13.1-0.20210401085038-d7dff0c84d17/go.mod h1:zU8KqE+6A+HksK4wiep8e/3UvCZLm+Wrw9AqZGaAm9k= github.com/thanos-io/thanos v0.22.0/go.mod h1:SZDWz3phcUcBr4MYFoPFRvl+Z9Nbi45HlwQlwSZSt+Q= +github.com/tidwall/gjson v1.6.0/go.mod h1:P256ACg0Mn+j1RXIDXoss50DeIABTYK1PULOJHhxOls= +github.com/tidwall/gjson v1.6.1/go.mod h1:BaHyNc5bjzYkPqgLq7mdVzeiRtULKULXLgZFKsxEHI0= github.com/tidwall/gjson v1.14.1/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= github.com/tidwall/pretty v0.0.0-20180105212114-65a9db5fad51/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= +github.com/tidwall/pretty v1.0.2 h1:Z7S3cePv9Jwm1KwS0513MRaoUe3S01WPbLNV40pwWZU= +github.com/tidwall/pretty v1.0.2/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk= github.com/tidwall/pretty v1.2.0 h1:RWIZEg2iJ8/g6fDDYzMpobmaoGh5OLl4AXtGUGPcqCs= github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= github.com/tidwall/tinylru v1.0.2/go.mod h1:HDVL7TsWeezQ4g44Um84TOVBMFcq7Xa9giqNc805KJ8= @@ -3120,6 +3132,8 @@ golang.org/x/net v0.0.0-20220325170049-de3da57026de/go.mod h1:CfG3xpIq0wQ8r1q4Su golang.org/x/net v0.0.0-20220401154927-543a649e0bdd/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/net v0.0.0-20220421235706-1d1ef9303861 h1:yssD99+7tqHWO5Gwh81phT+67hg+KttniBr6UnEXOY8= golang.org/x/net v0.0.0-20220421235706-1d1ef9303861/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4 h1:HVyaeDAYux4pnY+D/SiwmLOR36ewZ4iGQIIrtnuCjFA= +golang.org/x/net v0.0.0-20220425223048-2871e0cb64e4/go.mod h1:CfG3xpIq0wQ8r1q4Su4UZFWDARRcnwPjda9FqA0JpMk= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20181106182150-f42d05182288/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index f60e068b81a..129cca21556 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -59,4 +59,5 @@ export interface FeatureToggles { azureMonitorExperimentalUI?: boolean; traceToMetrics?: boolean; prometheusStreamingJSONParser?: boolean; + validateDashboardsOnSave?: boolean; } diff --git a/pkg/api/dashboard.go b/pkg/api/dashboard.go index 02ab0a43909..4fb8202bb71 100644 --- a/pkg/api/dashboard.go +++ b/pkg/api/dashboard.go @@ -16,11 +16,14 @@ import ( "github.com/grafana/grafana/pkg/api/response" "github.com/grafana/grafana/pkg/components/dashdiffs" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/coremodel/dashboard" + "github.com/grafana/grafana/pkg/cuectx" "github.com/grafana/grafana/pkg/infra/metrics" "github.com/grafana/grafana/pkg/models" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/dashboards" + "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/guardian" pref "github.com/grafana/grafana/pkg/services/preference" "github.com/grafana/grafana/pkg/services/star" @@ -305,6 +308,28 @@ func (hs *HTTPServer) PostDashboard(c *models.ReqContext) response.Response { if err := web.Bind(c.Req, &cmd); err != nil { return response.Error(http.StatusBadRequest, "bad request data", err) } + + if hs.Features.IsEnabled(featuremgmt.FlagValidateDashboardsOnSave) { + // Ideally, coremodel validation calls would be integrated into the web + // framework. But this does the job for now. + if cm, has := hs.CoremodelRegistry.Get("dashboard"); has { + schv, err := cmd.Dashboard.Get("schemaVersion").Int() + + // Only try to validate if the schemaVersion is at least the handoff version + // (the minimum schemaVersion against which the dashboard schema is known to + // work), or if schemaVersion is absent (which will happen once the Thema + // schema becomes canonical). + if err != nil || schv >= dashboard.HandoffSchemaVersion { + // Can't fail, web.Bind() already ensured it's valid JSON + b, _ := cmd.Dashboard.Bytes() + v, _ := cuectx.JSONtoCUE("dashboard.json", b) + if _, err := cm.CurrentSchema().Validate(v); err != nil { + return response.Error(http.StatusBadRequest, "invalid dashboard json", err) + } + } + } + } + return hs.postDashboard(c, cmd) } diff --git a/pkg/api/dashboard_test.go b/pkg/api/dashboard_test.go index 7084a15b404..b25bef370d2 100644 --- a/pkg/api/dashboard_test.go +++ b/pkg/api/dashboard_test.go @@ -9,6 +9,9 @@ import ( "net/http" "testing" + "github.com/grafana/grafana/pkg/coremodel/dashboard" + "github.com/grafana/grafana/pkg/cuectx" + "github.com/grafana/grafana/pkg/framework/coremodel" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/mock" "github.com/stretchr/testify/require" @@ -23,7 +26,7 @@ import ( "github.com/grafana/grafana/pkg/services/alerting" "github.com/grafana/grafana/pkg/services/dashboards" "github.com/grafana/grafana/pkg/services/dashboards/database" - service "github.com/grafana/grafana/pkg/services/dashboards/service" + "github.com/grafana/grafana/pkg/services/dashboards/service" "github.com/grafana/grafana/pkg/services/featuremgmt" "github.com/grafana/grafana/pkg/services/guardian" "github.com/grafana/grafana/pkg/services/libraryelements" @@ -51,6 +54,7 @@ func TestGetHomeDashboard(t *testing.T) { Cfg: cfg, pluginStore: &fakePluginStore{}, SQLStore: mockstore.NewSQLStoreMock(), + CoremodelRegistry: setupDashboardCoremodel(t), preferenceService: prefService, } @@ -127,12 +131,13 @@ func TestDashboardAPIEndpoint(t *testing.T) { mockSQLStore := mockstore.NewSQLStoreMock() hs := &HTTPServer{ - Cfg: setting.NewCfg(), - pluginStore: &fakePluginStore{}, - SQLStore: mockSQLStore, - AccessControl: accesscontrolmock.New(), - Features: featuremgmt.WithFeatures(), - dashboardService: dashboardService, + Cfg: setting.NewCfg(), + pluginStore: &fakePluginStore{}, + SQLStore: mockSQLStore, + CoremodelRegistry: setupDashboardCoremodel(t), + AccessControl: accesscontrolmock.New(), + Features: featuremgmt.WithFeatures(), + dashboardService: dashboardService, } setUp := func() { @@ -235,6 +240,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { Live: newTestLive(t, sql), LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, + CoremodelRegistry: setupDashboardCoremodel(t), SQLStore: mockSQLStore, AccessControl: accesscontrolmock.New(), dashboardService: dashboardService, @@ -914,6 +920,7 @@ func TestDashboardAPIEndpoint(t *testing.T) { LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, dashboardProvisioningService: mockDashboardProvisioningService{}, + CoremodelRegistry: setupDashboardCoremodel(t), SQLStore: mockSQLStore, AccessControl: accesscontrolmock.New(), dashboardService: dashboardService, @@ -958,6 +965,7 @@ func getDashboardShouldReturn200WithConfig(t *testing.T, sc *scenarioContext, pr LibraryElementService: &libraryElementsService, SQLStore: sc.sqlStore, ProvisioningService: provisioningService, + CoremodelRegistry: setupDashboardCoremodel(t), AccessControl: accesscontrolmock.New(), dashboardProvisioningService: service.ProvideDashboardService( cfg, dashboardStore, nil, features, @@ -1026,6 +1034,7 @@ func postDashboardScenario(t *testing.T, desc string, url string, routePattern s pluginStore: &fakePluginStore{}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, + CoremodelRegistry: setupDashboardCoremodel(t), dashboardService: dashboardService, folderService: folderService, Features: featuremgmt.WithFeatures(), @@ -1057,6 +1066,7 @@ func postDiffScenario(t *testing.T, desc string, url string, routePattern string QuotaService: "a.QuotaService{Cfg: cfg}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, + CoremodelRegistry: setupDashboardCoremodel(t), SQLStore: sqlmock, } @@ -1090,6 +1100,7 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout QuotaService: "a.QuotaService{Cfg: cfg}, LibraryPanelService: &mockLibraryPanelService{}, LibraryElementService: &mockLibraryElementService{}, + CoremodelRegistry: setupDashboardCoremodel(t), dashboardService: mock, SQLStore: sqlStore, Features: featuremgmt.WithFeatures(), @@ -1116,6 +1127,16 @@ func restoreDashboardVersionScenario(t *testing.T, desc string, url string, rout }) } +func setupDashboardCoremodel(t *testing.T) *coremodel.Registry { + // TODO abstract and generalize this further for wider reuse + t.Helper() + dcm, err := dashboard.ProvideCoremodel(cuectx.ProvideThemaLibrary()) + require.NoError(t, err) + reg, err := coremodel.NewRegistry(dcm) + require.NoError(t, err) + return reg +} + func (sc *scenarioContext) ToJSON() *simplejson.Json { result := simplejson.New() err := json.NewDecoder(sc.resp.Body).Decode(result) diff --git a/pkg/api/http_server.go b/pkg/api/http_server.go index 0c6872bba89..6f0a785bbcc 100644 --- a/pkg/api/http_server.go +++ b/pkg/api/http_server.go @@ -17,6 +17,7 @@ import ( "github.com/grafana/grafana/pkg/api/routing" httpstatic "github.com/grafana/grafana/pkg/api/static" "github.com/grafana/grafana/pkg/components/simplejson" + "github.com/grafana/grafana/pkg/framework/coremodel" "github.com/grafana/grafana/pkg/infra/localcache" "github.com/grafana/grafana/pkg/infra/log" "github.com/grafana/grafana/pkg/infra/remotecache" @@ -154,6 +155,7 @@ type HTTPServer struct { folderPermissionsService accesscontrol.FolderPermissionsService dashboardPermissionsService accesscontrol.DashboardPermissionsService starService star.Service + CoremodelRegistry *coremodel.Registry } type ServerOptions struct { @@ -187,7 +189,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi dashboardsnapshotsService *dashboardsnapshots.Service, commentsService *comments.Service, pluginSettings *pluginSettings.Service, avatarCacheServer *avatar.AvatarCacheServer, preferenceService pref.Service, entityEventsService store.EntityEventsService, teamsPermissionsService accesscontrol.TeamPermissionsService, folderPermissionsService accesscontrol.FolderPermissionsService, - dashboardPermissionsService accesscontrol.DashboardPermissionsService, starService star.Service, + dashboardPermissionsService accesscontrol.DashboardPermissionsService, starService star.Service, coremodelRegistry *coremodel.Registry, ) (*HTTPServer, error) { web.Env = cfg.Env m := web.New() @@ -265,6 +267,7 @@ func ProvideHTTPServer(opts ServerOptions, cfg *setting.Cfg, routeRegister routi folderPermissionsService: folderPermissionsService, dashboardPermissionsService: dashboardPermissionsService, starService: starService, + CoremodelRegistry: coremodelRegistry, } if hs.Listener != nil { hs.log.Debug("Using provided listener") diff --git a/pkg/coremodel/dashboard/schema.go b/pkg/coremodel/dashboard/schema.go index 687cd91dd9f..a0a04588d3f 100644 --- a/pkg/coremodel/dashboard/schema.go +++ b/pkg/coremodel/dashboard/schema.go @@ -92,9 +92,19 @@ type model struct { Refresh interface{} `json:"refresh"` // (bool|string) SchemaVersion int `json:"schemaVersion"` Links []struct { + Title string `json:"title"` + Type string `json:"type"` + Icon string `json:"icon,omitempty"` + Tooltip string `json:"tooltip,omitempty"` + Url string `json:"url,omitempty"` + Tags []string `json:"tags"` + AsDropdown bool `json:"asDropdown"` + TargetBlank bool `json:"targetBlank"` + IncludeVars bool `json:"includeVars"` + KeepTime bool `json:"keepTime"` } `json:"links"` Panels []interface{} `json:"panels"` - FiscalYearStartMonth string `json:"fiscalYearStartMonth"` + FiscalYearStartMonth uint8 `json:"fiscalYearStartMonth"` LiveNow bool `json:"liveNow"` WeekStart string `json:"weekStart"` diff --git a/pkg/framework/coremodel/registry.go b/pkg/framework/coremodel/registry.go index 0fe44809d2f..c8eea3f8872 100644 --- a/pkg/framework/coremodel/registry.go +++ b/pkg/framework/coremodel/registry.go @@ -76,3 +76,12 @@ func (r *Registry) addModels(models []Interface) error { return nil } + +// Get retrieves a coremodel with the given string identifier. nil, false +// is returned if no such coremodel exists. +func (r *Registry) Get(name string) (cm Interface, has bool) { + r.lock.RLock() + cm, has = r.modelIdx[name] + r.lock.RUnlock() + return +} diff --git a/pkg/framework/coremodel/staticregistry/provide.go b/pkg/framework/coremodel/staticregistry/provide.go index 09a12bd3c77..9e2487f2846 100644 --- a/pkg/framework/coremodel/staticregistry/provide.go +++ b/pkg/framework/coremodel/staticregistry/provide.go @@ -11,7 +11,9 @@ import ( func ProvideRegistry( dashboard *dashboard.Coremodel, ) (*coremodel.Registry, error) { - return coremodel.NewRegistry( + cmlist := []coremodel.Interface{ dashboard, - ) + } + + return coremodel.NewRegistry(cmlist...) } diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index bcda43ec5e0..e17dfdfeb53 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -243,5 +243,11 @@ var ( Description: "Enable streaming JSON parser for Prometheus datasource", State: FeatureStateAlpha, }, + { + Name: "validateDashboardsOnSave", + Description: "Validate dashboard JSON POSTed to api/dashboards/db", + State: FeatureStateAlpha, + RequiresRestart: true, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 76c6c377770..76d765c3624 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -178,4 +178,8 @@ const ( // FlagPrometheusStreamingJSONParser // Enable streaming JSON parser for Prometheus datasource FlagPrometheusStreamingJSONParser = "prometheusStreamingJSONParser" + + // FlagValidateDashboardsOnSave + // Validate dashboard JSON POSTed to api/dashboards/db + FlagValidateDashboardsOnSave = "validateDashboardsOnSave" )