Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs

This commit is contained in:
Ryan McKinley
2025-06-30 13:47:39 -07:00
330 changed files with 8259 additions and 4001 deletions
+1 -1
View File
@@ -418,7 +418,7 @@ func TestHTTPServer_GetDashboardVersions_AccessControl(t *testing.T) {
})
}
func TestDashboardAPIEndpoint(t *testing.T) {
func TestIntegrationDashboardAPIEndpoint(t *testing.T) {
t.Run("Given two dashboards with the same title in different folders", func(t *testing.T) {
dashOne := dashboards.NewDashboard("dash")
dashOne.ID = 2
+4 -4
View File
@@ -113,7 +113,7 @@ func setupTestEnvironment(t *testing.T, cfg *setting.Cfg, features featuremgmt.F
return m, hs
}
func TestHTTPServer_GetFrontendSettings_hideVersionAnonymous(t *testing.T) {
func TestIntegrationHTTPServer_GetFrontendSettings_hideVersionAnonymous(t *testing.T) {
type buildInfo struct {
Version string `json:"version"`
Commit string `json:"commit"`
@@ -182,7 +182,7 @@ func TestHTTPServer_GetFrontendSettings_hideVersionAnonymous(t *testing.T) {
}
}
func TestHTTPServer_GetFrontendSettings_pluginsCDNBaseURL(t *testing.T) {
func TestIntegrationHTTPServer_GetFrontendSettings_pluginsCDNBaseURL(t *testing.T) {
type settings struct {
PluginsCDNBaseURL string `json:"pluginsCDNBaseURL"`
}
@@ -232,7 +232,7 @@ func TestHTTPServer_GetFrontendSettings_pluginsCDNBaseURL(t *testing.T) {
}
}
func TestHTTPServer_GetFrontendSettings_apps(t *testing.T) {
func TestIntegrationHTTPServer_GetFrontendSettings_apps(t *testing.T) {
type settings struct {
Apps map[string]*plugins.AppDTO `json:"apps"`
}
@@ -462,7 +462,7 @@ func newAppSettings(id string, enabled bool) map[string]*pluginsettings.DTO {
}
}
func TestHTTPServer_GetFrontendSettings_translations(t *testing.T) {
func TestIntegrationHTTPServer_GetFrontendSettings_translations(t *testing.T) {
type settings struct {
Datasources map[string]plugins.DataSourceDTO `json:"datasources"`
Panels map[string]*plugins.PanelDTO `json:"panels"`
+3 -3
View File
@@ -292,16 +292,16 @@ func (hs *HTTPServer) DeleteOrgByID(c *contextmodel.ReqContext) response.Respons
if err != nil {
return response.Error(http.StatusBadRequest, "orgId is invalid", err)
}
// before deleting an org, check if user does not belong to the current org
// before deleting an org, check if user is not active in the org
if c.GetOrgID() == orgID {
return response.Error(http.StatusBadRequest, "Can not delete org for current user", nil)
return response.Error(http.StatusBadRequest, "Cannot delete your active organization. Please switch to a different organization first.", nil)
}
if err := hs.orgDeletionService.Delete(c.Req.Context(), &org.DeleteOrgCommand{ID: orgID}); err != nil {
if errors.Is(err, org.ErrOrgNotFound) {
return response.Error(http.StatusNotFound, "Failed to delete organization. ID not found", nil)
}
return response.Error(http.StatusInternalServerError, "Failed to update organization", err)
return response.Error(http.StatusInternalServerError, "Failed to delete organization", err)
}
return response.Success("Organization deleted")
}
+1 -1
View File
@@ -64,7 +64,7 @@ func setUpGetOrgUsersDB(t *testing.T, sqlStore db.DB, cfg *setting.Cfg) {
require.NoError(t, err)
}
func TestOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
func TestIntegrationOrgUsersAPIEndpoint_userLoggedIn(t *testing.T) {
hs := setupSimpleHTTPServer(featuremgmt.WithFeatures())
settings := hs.Cfg
+1 -1
View File
@@ -41,7 +41,7 @@ import (
"github.com/grafana/grafana/pkg/web/webtest"
)
func TestCallResource(t *testing.T) {
func TestIntegrationCallResource(t *testing.T) {
staticRootPath, err := filepath.Abs("../../public/")
require.NoError(t, err)
+1 -1
View File
@@ -60,7 +60,7 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestDataSourceProxy_routeRule(t *testing.T) {
func TestIntegrationDataSourceProxy_routeRule(t *testing.T) {
cfg := &setting.Cfg{}
t.Run("Plugin with routes", func(t *testing.T) {
+4 -4
View File
@@ -57,7 +57,7 @@ import (
const newEmail = "newemail@localhost"
func TestUserAPIEndpoint_userLoggedIn(t *testing.T) {
func TestIntegrationUserAPIEndpoint_userLoggedIn(t *testing.T) {
settings := setting.NewCfg()
sqlStore := db.InitTestDB(t, sqlstore.InitTestDBOpt{Cfg: settings})
hs := &HTTPServer{
@@ -404,7 +404,7 @@ func Test_GetUserByID(t *testing.T) {
}
}
func TestHTTPServer_UpdateUser(t *testing.T) {
func TestIntegrationHTTPServer_UpdateUser(t *testing.T) {
settings := setting.NewCfg()
sqlStore := db.InitTestDB(t)
@@ -478,7 +478,7 @@ func setupUpdateEmailTests(t *testing.T, cfg *setting.Cfg) (*user.User, *HTTPSer
return usr, hs, nsMock
}
func TestUser_UpdateEmail(t *testing.T) {
func TestIntegrationUser_UpdateEmail(t *testing.T) {
cases := []struct {
Name string
Field user.UpdateEmailActionType
@@ -1153,7 +1153,7 @@ func updateUserScenario(t *testing.T, ctx updateUserContext, hs *HTTPServer) {
})
}
func TestHTTPServer_UpdateSignedInUser(t *testing.T) {
func TestIntegrationHTTPServer_UpdateSignedInUser(t *testing.T) {
settings := setting.NewCfg()
sqlStore := db.InitTestDB(t)
+3
View File
@@ -101,6 +101,7 @@ type genCmd struct {
headerFile string
prefixFileName string
tags string
genTags string
}
func (*genCmd) Name() string { return "gen" }
@@ -119,6 +120,7 @@ func (cmd *genCmd) SetFlags(f *flag.FlagSet) {
f.StringVar(&cmd.headerFile, "header_file", "", "path to file to insert as a header in wire_gen.go")
f.StringVar(&cmd.prefixFileName, "output_file_prefix", "", "string to prepend to output file names.")
f.StringVar(&cmd.tags, "tags", "", "append build tags to the default wirebuild")
f.StringVar(&cmd.genTags, "gen_tags", "", "append build tags to the generated file")
}
func (cmd *genCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) subcommands.ExitStatus {
@@ -135,6 +137,7 @@ func (cmd *genCmd) Execute(ctx context.Context, f *flag.FlagSet, args ...interfa
opts.PrefixOutputFile = cmd.prefixFileName
opts.Tags = cmd.tags
opts.GenTags = cmd.genTags
outs, errs := wire.Generate(ctx, wd, os.Environ(), packages(f), opts)
if len(errs) > 0 {
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -2,9 +2,8 @@
//
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
@@ -1,8 +1,7 @@
// Code generated by Wire. DO NOT EDIT.
//go:generate go run -mod=mod github.com/google/wire/cmd/wire
//go:generate go run ./pkg/build/wire/cmd/wire/main.go
//go:build !wireinject
// +build !wireinject
package main
+9 -4
View File
@@ -66,6 +66,7 @@ type GenerateOptions struct {
Header []byte
PrefixOutputFile string
Tags string
GenTags string
}
// Generate performs dependency injection for the packages that match the given
@@ -104,7 +105,7 @@ func Generate(ctx context.Context, wd string, env []string, patterns []string, o
continue
}
copyNonInjectorDecls(g, injectorFiles, pkg.TypesInfo)
goSrc := g.frame(opts.Tags)
goSrc := g.frame(opts.Tags, opts.GenTags)
if len(opts.Header) > 0 {
goSrc = append(opts.Header, goSrc...)
}
@@ -258,7 +259,7 @@ func newGen(pkg *packages.Package) *gen {
}
// frame bakes the built up source body into an unformatted Go source file.
func (g *gen) frame(tags string) []byte {
func (g *gen) frame(tags, genTags string) []byte {
if g.buf.Len() == 0 {
return nil
}
@@ -267,8 +268,12 @@ func (g *gen) frame(tags string) []byte {
tags = fmt.Sprintf(" gen -tags \"%s\"", tags)
}
buf.WriteString("// Code generated by Wire. DO NOT EDIT.\n\n")
buf.WriteString("//go:generate go run -mod=mod github.com/google/wire/cmd/wire" + tags + "\n")
buf.WriteString("//+build !wireinject\n\n")
buf.WriteString("//go:generate go run ./pkg/build/wire/cmd/wire/main.go" + tags + "\n")
buildTags := "!wireinject"
if len(genTags) > 0 {
buildTags += " && " + genTags
}
buf.WriteString("//go:build " + buildTags + "\n\n")
buf.WriteString("package ")
buf.WriteString(g.pkg.Name)
buf.WriteString("\n\n")
@@ -20,7 +20,7 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestPasswordMigrationCommand(t *testing.T) {
func TestIntegrationPasswordMigrationCommand(t *testing.T) {
// setup datasources with password, basic_auth and none
store := db.InitTestDB(t)
err := store.WithDbSession(context.Background(), func(sess *db.Session) error {
@@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/search/sort"
"github.com/grafana/grafana/pkg/setting"
@@ -75,6 +76,7 @@ func ToUnifiedStorage(c utils.CommandLine, cfg *setting.Cfg, sqlStore db.DB) err
provisioning,
nil, // no librarypanels.Service
sort.ProvideService(),
acimpl.ProvideAccessControl(featuremgmt.WithFeatures()),
)
client, err := newUnifiedClient(cfg, sqlStore)
@@ -10,7 +10,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
)
func TestUnifiedStorageCommand(t *testing.T) {
func TestIntegrationUnifiedStorageCommand(t *testing.T) {
// setup datasources with password, basic_auth and none
store := db.InitTestDB(t)
err := store.WithDbSession(context.Background(), func(sess *db.Session) error {
+1 -1
View File
@@ -248,7 +248,7 @@ func TestIntegrationKVStore(t *testing.T) {
})
}
func TestGetItems(t *testing.T) {
func TestIntegrationGetItems(t *testing.T) {
kv := createTestableKVStore(t)
ctx := context.Background()
@@ -11,7 +11,7 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
)
func TestDatabaseStorageGarbageCollection(t *testing.T) {
func TestIntegrationDatabaseStorageGarbageCollection(t *testing.T) {
sqlstore := db.InitTestDB(t)
db := &databaseCache{
@@ -58,7 +58,7 @@ func TestDatabaseStorageGarbageCollection(t *testing.T) {
assert.Equal(t, err, nil)
}
func TestSecondSet(t *testing.T) {
func TestIntegrationSecondSet(t *testing.T) {
var err error
sqlstore := db.InitTestDB(t)
+1 -1
View File
@@ -33,7 +33,7 @@ func createTestClient(t *testing.T, opts *setting.RemoteCacheSettings, sqlstore
return dc
}
func TestCachedBasedOnConfig(t *testing.T) {
func TestIntegrationCachedBasedOnConfig(t *testing.T) {
db, cfg := sqlstore.InitTestDB(t)
err := cfg.Load(setting.CommandLineArgs{
HomePath: "../../../",
+2 -2
View File
@@ -30,7 +30,7 @@ func createTestableServerLock(t *testing.T) *ServerLockService {
}
}
func TestServerLock(t *testing.T) {
func TestIntegrationServerLock(t *testing.T) {
sl := createTestableServerLock(t)
operationUID := "test-operation"
@@ -66,7 +66,7 @@ func TestServerLock(t *testing.T) {
})
}
func TestLockAndRelease(t *testing.T) {
func TestIntegrationLockAndRelease(t *testing.T) {
operationUID := "test-operation-release"
t.Run("create lock and then release it", func(t *testing.T) {
@@ -155,7 +155,7 @@ func TestMetrics(t *testing.T) {
})
}
func TestGetUsageReport_IncludesMetrics(t *testing.T) {
func TestIntegrationGetUsageReport_IncludesMetrics(t *testing.T) {
sqlStore := dbtest.NewFakeDB()
uss := createService(t, sqlStore, true)
metricName := "stats.test_metric.count"
@@ -27,7 +27,7 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestConcurrentUsersMetrics(t *testing.T) {
func TestIntegrationConcurrentUsersMetrics(t *testing.T) {
sqlStore, cfg := db.InitTestDBWithCfg(t)
statsService := statsimpl.ProvideService(&setting.Cfg{}, sqlStore, &dashboards.FakeDashboardService{}, &foldertest.FakeService{}, &orgtest.FakeOrgService{}, featuremgmt.WithFeatures())
s := createService(t, cfg, sqlStore, statsService)
@@ -45,7 +45,7 @@ func TestConcurrentUsersMetrics(t *testing.T) {
assert.Equal(t, int32(6), stats["stats.auth_token_per_user_le_inf"])
}
func TestConcurrentUsersStats(t *testing.T) {
func TestIntegrationConcurrentUsersStats(t *testing.T) {
sqlStore, cfg := db.InitTestDBWithCfg(t)
statsService := statsimpl.ProvideService(&setting.Cfg{}, sqlStore, &dashboards.FakeDashboardService{}, &foldertest.FakeService{}, &orgtest.FakeOrgService{}, featuremgmt.WithFeatures())
s := createService(t, cfg, sqlStore, statsService)
+2 -2
View File
@@ -27,7 +27,7 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestSocialService_ProvideService(t *testing.T) {
func TestIntegrationSocialService_ProvideService(t *testing.T) {
type testEnv struct {
features featuremgmt.FeatureToggles
}
@@ -138,7 +138,7 @@ func TestSocialService_ProvideService(t *testing.T) {
}
}
func TestSocialService_ProvideService_GrafanaComGrafanaNet(t *testing.T) {
func TestIntegrationSocialService_ProvideService_GrafanaComGrafanaNet(t *testing.T) {
testCases := []struct {
name string
rawIniContent string
+1 -1
View File
@@ -156,7 +156,7 @@ func TestCheckHealth(t *testing.T) {
})
}
func TestCallResource(t *testing.T) {
func TestIntegrationCallResource(t *testing.T) {
registry := fakes.NewFakePluginRegistry()
p := &plugins.Plugin{
JSONData: plugins.JSONData{
+83 -32
View File
@@ -6,10 +6,12 @@ import (
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/authlib/types"
dashv0 "github.com/grafana/grafana/apps/dashboard/pkg/apis/dashboard/v0alpha1"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/libraryelements"
)
func GetAuthorizer(ac accesscontrol.AccessControl, l log.Logger) authorizer.Authorizer {
@@ -42,38 +44,87 @@ func GetAuthorizer(ac accesscontrol.AccessControl, l log.Logger) authorizer.Auth
return authorizer.DecisionDeny, "org mismatch", dashboards.ErrUserIsNotSignedInToOrg
}
switch attr.GetVerb() {
case "list", "search":
// Detailed read permissions are handled by authz, this just checks whether the user can ready *any* dashboard
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not read any dashboards", err
}
case "create":
// Detailed create permissions are handled by authz, this just checks whether the user can create *any* dashboard
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not create any dashboards", err
}
case "get":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not view dashboard", err
}
case "update", "patch":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not edit dashboard", err
}
case "delete":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not delete dashboard", err
}
default:
l.Info("unknown verb", "verb", attr.GetVerb())
return authorizer.DecisionDeny, "unsupported verb", nil // Unknown verb
// Determine if this is a library panel or dashboard resource
resource := attr.GetResource()
isLibraryPanel := resource == dashv0.LIBRARY_PANEL_RESOURCE
if isLibraryPanel {
return authorizeLibraryPanel(ctx, ac, user, attr)
} else {
return authorizeDashboard(ctx, ac, user, attr)
}
return authorizer.DecisionAllow, "", nil
})
}
func authorizeLibraryPanel(ctx context.Context, ac accesscontrol.AccessControl, user identity.Requester, attr authorizer.Attributes) (authorizer.Decision, string, error) {
switch attr.GetVerb() {
case "list", "search":
// Detailed read permissions are handled by authz, this just checks whether the user can ready *any* library panel
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsRead))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not read any library panels", err
}
case "create":
// TODO: uncomment this when we implement create :)
//
// Detailed create permissions are handled by authz, this just checks whether the user can create *any* library panel
// ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsCreate))
// if !ok || err != nil {
// return authorizer.DecisionDeny, "can not create any library panels", err
// }
return authorizer.DecisionDeny, "can not create any library panels", nil
case "get":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsRead, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not view library panel", err
}
case "update", "patch":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsWrite, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not edit library panel", err
}
case "delete":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(libraryelements.ActionLibraryPanelsDelete, libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not delete library panel", err
}
default:
return authorizer.DecisionDeny, "unsupported verb for library panels", nil
}
return authorizer.DecisionAllow, "", nil
}
func authorizeDashboard(ctx context.Context, ac accesscontrol.AccessControl, user identity.Requester, attr authorizer.Attributes) (authorizer.Decision, string, error) {
switch attr.GetVerb() {
case "list", "search":
// Detailed read permissions are handled by authz, this just checks whether the user can ready *any* dashboard
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not read any dashboards", err
}
case "create":
// Detailed create permissions are handled by authz, this just checks whether the user can create *any* dashboard
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsCreate))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not create any dashboards", err
}
case "get":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsRead, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not view dashboard", err
}
case "update", "patch":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsWrite, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not edit dashboard", err
}
case "delete":
ok, err := ac.Evaluate(ctx, user, accesscontrol.EvalPermission(dashboards.ActionDashboardsDelete, dashboards.ScopeDashboardsProvider.GetResourceScopeUID(attr.GetName())))
if !ok || err != nil {
return authorizer.DecisionDeny, "can not delete dashboard", err
}
default:
return authorizer.DecisionDeny, "unsupported verb for dashboards", nil
}
return authorizer.DecisionAllow, "", nil
}
@@ -15,6 +15,7 @@ import (
folders "github.com/grafana/grafana/apps/folder/pkg/apis/folder/v1beta1"
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/librarypanels"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/search/sort"
@@ -48,9 +49,10 @@ func ProvideLegacyMigrator(
sql db.DB, // direct access to tables
provisioning provisioning.ProvisioningService, // only needed for dashboard settings
libraryPanelSvc librarypanels.Service,
accessControl accesscontrol.AccessControl,
) LegacyMigrator {
dbp := legacysql.NewDatabaseProvider(sql)
return NewDashboardAccess(dbp, authlib.OrgNamespaceFormatter, nil, provisioning, libraryPanelSvc, sort.ProvideService())
return NewDashboardAccess(dbp, authlib.OrgNamespaceFormatter, nil, provisioning, libraryPanelSvc, sort.ProvideService(), accessControl)
}
type BlobStoreInfo struct {
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM {{ .Ident .LibraryElementTable }} as p
LEFT OUTER JOIN {{ .Ident .UserTable }} AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN {{ .Ident .UserTable }} AS updated_user ON p.updated_by = updated_user.id
@@ -25,9 +25,11 @@ import (
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacysearcher"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/libraryelements"
"github.com/grafana/grafana/pkg/services/librarypanels"
"github.com/grafana/grafana/pkg/services/provisioning"
"github.com/grafana/grafana/pkg/services/search/sort"
@@ -64,6 +66,7 @@ type dashboardSqlAccess struct {
dashStore dashboards.Store
dashboardSearchClient legacysearcher.DashboardSearchClient
accessControl accesscontrol.AccessControl
libraryPanelSvc librarypanels.Service
// Typically one... the server wrapper
@@ -78,6 +81,7 @@ func NewDashboardAccess(sql legacysql.LegacyDatabaseProvider,
provisioning provisioning.ProvisioningService,
libraryPanelSvc librarypanels.Service,
sorter sort.Service,
accessControl accesscontrol.AccessControl,
) DashboardAccess {
dashboardSearchClient := legacysearcher.NewDashboardSearchClient(dashStore, sorter)
return &dashboardSqlAccess{
@@ -87,6 +91,7 @@ func NewDashboardAccess(sql legacysql.LegacyDatabaseProvider,
provisioning: provisioning,
dashboardSearchClient: *dashboardSearchClient,
libraryPanelSvc: libraryPanelSvc,
accessControl: accessControl,
log: log.New("dashboard.legacysql"),
}
}
@@ -478,6 +483,25 @@ func (a *dashboardSqlAccess) SaveDashboard(ctx context.Context, orgId int64, das
return dash, created, err
}
type panel struct {
ID int64
UID string
FolderUID sql.NullString
Created time.Time
CreatedBy string
Updated time.Time
UpdatedBy string
Version int64
Name string
Type string
Description string
Model []byte
}
func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query LibraryPanelQuery) (*dashboardV0.LibraryPanelList, error) {
limit := int(query.Limit)
query.Limit += 1 // for continue
@@ -485,6 +509,11 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library
return nil, fmt.Errorf("expected non zero orgID")
}
user, err := identity.GetRequester(ctx)
if err != nil {
return nil, err
}
sqlx, err := a.sql(ctx)
if err != nil {
return nil, err
@@ -508,93 +537,30 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library
return nil, err
}
type panel struct {
ID int64
UID string
FolderUID sql.NullString
Created time.Time
CreatedBy string
Updated time.Time
UpdatedBy string
Name string
Type string
Description string
Model []byte
}
var lastID int64
for rows.Next() {
p := panel{}
err = rows.Scan(&p.ID, &p.UID, &p.FolderUID,
&p.Created, &p.CreatedBy,
&p.Updated, &p.UpdatedBy,
&p.Name, &p.Type, &p.Description, &p.Model,
&p.Name, &p.Type, &p.Description, &p.Model, &p.Version,
)
if err != nil {
return res, err
}
lastID = p.ID
item := dashboardV0.LibraryPanel{
TypeMeta: metav1.TypeMeta{
APIVersion: dashboardV0.APIVERSION,
Kind: "LibraryPanel",
},
ObjectMeta: metav1.ObjectMeta{
Name: p.UID,
CreationTimestamp: metav1.NewTime(p.Created),
ResourceVersion: strconv.FormatInt(p.Updated.UnixMicro(), 10),
},
Spec: dashboardV0.LibraryPanelSpec{},
}
status := &dashboardV0.LibraryPanelStatus{
Missing: v0alpha1.Unstructured{},
}
err = json.Unmarshal(p.Model, &item.Spec)
item, err := parseLibraryPanelRow(p)
if err != nil {
return nil, err
}
err = json.Unmarshal(p.Model, &status.Missing.Object)
if err != nil {
return nil, err
return res, err
}
if item.Spec.Title != p.Name {
status.Warnings = append(status.Warnings, fmt.Sprintf("title mismatch (expected: %s)", p.Name))
}
if item.Spec.Description != p.Description {
status.Warnings = append(status.Warnings, fmt.Sprintf("description mismatch (expected: %s)", p.Description))
}
if item.Spec.Type != p.Type {
status.Warnings = append(status.Warnings, fmt.Sprintf("type mismatch (expected: %s)", p.Type))
}
item.Status = status
// Remove the properties we are already showing
for _, k := range []string{"type", "pluginVersion", "title", "description", "options", "fieldConfig", "datasource", "targets", "libraryPanel"} {
delete(status.Missing.Object, k)
}
meta, err := utils.MetaAccessor(&item)
if err != nil {
return nil, err
}
if p.FolderUID.Valid {
meta.SetFolder(p.FolderUID.String)
}
meta.SetCreatedBy(p.CreatedBy)
meta.SetGeneration(1)
meta.SetDeprecatedInternalID(p.ID) //nolint:staticcheck
// Only set updated metadata if it is different
if p.UpdatedBy != p.CreatedBy || p.Updated.Sub(p.Created) > time.Second {
meta.SetUpdatedBy(p.UpdatedBy)
meta.SetUpdatedTimestamp(&p.Updated)
meta.SetGeneration(2)
ok, err := a.accessControl.Evaluate(ctx, user, accesscontrol.EvalPermission(
libraryelements.ActionLibraryPanelsRead,
libraryelements.ScopeLibraryPanelsProvider.GetResourceScopeUID(item.Name),
))
if err != nil || !ok {
continue
}
res.Items = append(res.Items, item)
@@ -611,3 +577,71 @@ func (a *dashboardSqlAccess) GetLibraryPanels(ctx context.Context, query Library
}
return res, err
}
func parseLibraryPanelRow(p panel) (dashboardV0.LibraryPanel, error) {
item := dashboardV0.LibraryPanel{
TypeMeta: metav1.TypeMeta{
APIVersion: dashboardV0.APIVERSION,
Kind: "LibraryPanel",
},
ObjectMeta: metav1.ObjectMeta{
Name: p.UID,
CreationTimestamp: metav1.NewTime(p.Created),
ResourceVersion: strconv.FormatInt(p.Updated.UnixMicro(), 10),
},
Spec: dashboardV0.LibraryPanelSpec{},
}
status := &dashboardV0.LibraryPanelStatus{
Missing: v0alpha1.Unstructured{},
}
err := json.Unmarshal(p.Model, &item.Spec)
if err != nil {
return item, err
}
err = json.Unmarshal(p.Model, &status.Missing.Object)
if err != nil {
return item, err
}
// the panel title used in dashboards and title of the library panel can differ
// in the old model blob, the panel title is specified as "title", and the library panel title is
// in "libraryPanel.name", or as the column in the db.
item.Spec.PanelTitle = item.Spec.Title
item.Spec.Title = p.Name
if item.Spec.Title != p.Name {
status.Warnings = append(status.Warnings, fmt.Sprintf("title mismatch (expected: %s)", p.Name))
}
if item.Spec.Description != p.Description {
status.Warnings = append(status.Warnings, fmt.Sprintf("description mismatch (expected: %s)", p.Description))
}
if item.Spec.Type != p.Type {
status.Warnings = append(status.Warnings, fmt.Sprintf("type mismatch (expected: %s)", p.Type))
}
item.Status = status
// Remove the properties we are already showing
for _, k := range []string{"type", "pluginVersion", "title", "description", "options", "fieldConfig", "datasource", "targets", "libraryPanel", "id", "gridPos"} {
delete(status.Missing.Object, k)
}
meta, err := utils.MetaAccessor(&item)
if err != nil {
return item, err
}
if p.FolderUID.Valid {
meta.SetFolder(p.FolderUID.String)
}
meta.SetCreatedBy(p.CreatedBy)
meta.SetGeneration(p.Version)
meta.SetDeprecatedInternalID(p.ID) //nolint:staticcheck
// Only set updated metadata if it is different
if p.UpdatedBy != p.CreatedBy || p.Updated.Sub(p.Created) > time.Second {
meta.SetUpdatedBy(p.UpdatedBy)
meta.SetUpdatedTimestamp(&p.Updated)
}
return item, nil
}
@@ -2,6 +2,8 @@ package legacy
import (
"context"
"database/sql"
"encoding/json"
"testing"
"time"
@@ -221,3 +223,81 @@ func TestBuildSaveDashboardCommand(t *testing.T) {
})
}
}
func TestParseLibraryPanelRow(t *testing.T) {
basePanel := panel{
ID: 1,
UID: "panel-uid",
FolderUID: sql.NullString{String: "folder-uid", Valid: true},
Created: time.Now(),
CreatedBy: "creator",
Updated: time.Now(),
UpdatedBy: "updator",
Version: 2,
Type: "graph",
Description: "desc from db",
}
t.Run("type mismatch triggers warning", func(t *testing.T) {
p := basePanel
p.Name = "Test Panel"
p.Type = "graph"
model := map[string]interface{}{
"title": "Test Panel",
"type": "table",
"description": "desc from db",
}
modelBytes, err := json.Marshal(model)
require.NoError(t, err)
p.Model = modelBytes
item, err := parseLibraryPanelRow(p)
require.NoError(t, err)
require.Equal(t, "table", item.Spec.Type)
require.NotEmpty(t, item.Status.Warnings)
require.Contains(t, item.Status.Warnings[0], "type mismatch")
})
t.Run("metadata is set correctly", func(t *testing.T) {
p := basePanel
p.Name = "Test Panel"
model := map[string]interface{}{
"title": "Test Panel",
"type": "graph",
"description": "desc from db",
}
modelBytes, err := json.Marshal(model)
require.NoError(t, err)
p.Model = modelBytes
item, err := parseLibraryPanelRow(p)
require.NoError(t, err)
meta, err := utils.MetaAccessor(&item)
require.NoError(t, err)
require.Equal(t, p.ID, meta.GetDeprecatedInternalID()) // nolint:staticcheck
require.Equal(t, p.Version, meta.GetGeneration())
require.Equal(t, p.FolderUID.String, meta.GetFolder())
require.Equal(t, p.CreatedBy, meta.GetCreatedBy())
require.Equal(t, p.UpdatedBy, meta.GetUpdatedBy())
})
t.Run("panel title in dashboard vs library panel title is set correctly", func(t *testing.T) {
p := basePanel
p.Name = "Database Name"
model := map[string]interface{}{
"title": "Model Title",
"type": "graph",
"description": "desc from db",
}
modelBytes, err := json.Marshal(model)
require.NoError(t, err)
p.Model = modelBytes
item, err := parseLibraryPanelRow(p)
require.NoError(t, err)
require.Equal(t, "Model Title", item.Spec.PanelTitle)
require.Equal(t, "Database Name", item.Spec.Title)
})
}
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM `grafana`.`library_element` as p
LEFT OUTER JOIN `grafana`.`user` AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN `grafana`.`user` AS updated_user ON p.updated_by = updated_user.id
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM `grafana`.`library_element` as p
LEFT OUTER JOIN `grafana`.`user` AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN `grafana`.`user` AS updated_user ON p.updated_by = updated_user.id
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM `grafana`.`library_element` as p
LEFT OUTER JOIN `grafana`.`user` AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN `grafana`.`user` AS updated_user ON p.updated_by = updated_user.id
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM "grafana"."library_element" as p
LEFT OUTER JOIN "grafana"."user" AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN "grafana"."user" AS updated_user ON p.updated_by = updated_user.id
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM "grafana"."library_element" as p
LEFT OUTER JOIN "grafana"."user" AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN "grafana"."user" AS updated_user ON p.updated_by = updated_user.id
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM "grafana"."library_element" as p
LEFT OUTER JOIN "grafana"."user" AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN "grafana"."user" AS updated_user ON p.updated_by = updated_user.id
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM "grafana"."library_element" as p
LEFT OUTER JOIN "grafana"."user" AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN "grafana"."user" AS updated_user ON p.updated_by = updated_user.id
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM "grafana"."library_element" as p
LEFT OUTER JOIN "grafana"."user" AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN "grafana"."user" AS updated_user ON p.updated_by = updated_user.id
@@ -1,7 +1,7 @@
SELECT p.id, p.uid, p.folder_uid,
p.created, created_user.uid as created_by,
p.updated, updated_user.uid as updated_by,
p.name, p.type, p.description, p.model
p.name, p.type, p.description, p.model, p.version
FROM "grafana"."library_element" as p
LEFT OUTER JOIN "grafana"."user" AS created_user ON p.created_by = created_user.id
LEFT OUTER JOIN "grafana"."user" AS updated_user ON p.updated_by = updated_user.id
+23 -2
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/apimachinery/utils"
"github.com/grafana/grafana/pkg/registry/apis/dashboard/legacy"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
apierrors "k8s.io/apimachinery/pkg/api/errors"
)
@@ -21,12 +22,16 @@ var (
_ rest.SingularNameProvider = (*LibraryPanelStore)(nil)
_ rest.Getter = (*LibraryPanelStore)(nil)
_ rest.Lister = (*LibraryPanelStore)(nil)
_ rest.Creater = (*LibraryPanelStore)(nil)
_ rest.GracefulDeleter = (*LibraryPanelStore)(nil)
_ rest.Updater = (*LibraryPanelStore)(nil)
_ rest.Storage = (*LibraryPanelStore)(nil)
)
type LibraryPanelStore struct {
Access legacy.DashboardAccess
ResourceInfo utils.ResourceInfo
Access legacy.DashboardAccess
ResourceInfo utils.ResourceInfo
AccessControl accesscontrol.AccessControl
}
func (s *LibraryPanelStore) New() runtime.Object {
@@ -51,6 +56,22 @@ func (s *LibraryPanelStore) ConvertToTable(ctx context.Context, object runtime.O
return s.ResourceInfo.TableConverter().ConvertToTable(ctx, object, tableOptions)
}
func (s *LibraryPanelStore) Create(ctx context.Context, obj runtime.Object, createValidation rest.ValidateObjectFunc, options *metav1.CreateOptions) (runtime.Object, error) {
return nil, fmt.Errorf("method not yet implemented")
}
func (s *LibraryPanelStore) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, createValidation rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, forceAllowCreate bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
return nil, false, fmt.Errorf("method not yet implemented")
}
func (s *LibraryPanelStore) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) {
return nil, false, fmt.Errorf("method not yet implemented")
}
func (s *LibraryPanelStore) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) {
return nil, apierrors.NewMethodNotSupported(s.ResourceInfo.GroupResource(), "deletecollection")
}
func (s *LibraryPanelStore) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
if options.ResourceVersion != "" {
return nil, apierrors.NewBadRequest("List with explicit resourceVersion is not supported with this storage backend")
+16 -4
View File
@@ -142,7 +142,7 @@ func RegisterAPIService(
folderClient: folderClient,
legacy: &DashboardStorage{
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, libraryPanelSvc, sorter),
Access: legacy.NewDashboardAccess(dbp, namespacer, dashStore, provisioning, libraryPanelSvc, sorter, accessControl),
DashboardService: dashboardService,
},
reg: reg,
@@ -519,9 +519,21 @@ func (b *DashboardsAPIBuilder) storageForVersion(
// Expose read only library panels
if libraryPanels != nil {
storage[libraryPanels.StoragePath()] = &LibraryPanelStore{
Access: b.legacy.Access,
ResourceInfo: *libraryPanels,
legacyLibraryStore := &LibraryPanelStore{
Access: b.legacy.Access,
ResourceInfo: *libraryPanels,
AccessControl: b.accessControl,
}
unifiedLibraryStore, err := grafanaregistry.NewRegistryStore(opts.Scheme, *libraryPanels, opts.OptsGetter)
if err != nil {
return err
}
libraryGr := libraryPanels.GroupResource()
storage[libraryPanels.StoragePath()], err = opts.DualWriteBuilder(libraryGr, legacyLibraryStore, unifiedLibraryStore)
if err != nil {
return err
}
}
@@ -0,0 +1,35 @@
package contracts
import (
"context"
"errors"
"time"
"github.com/grafana/grafana/pkg/registry/apis/secret/encryption"
)
var (
ErrDataKeyNotFound = errors.New("data key not found")
)
// SecretDataKey does not have a mirrored K8s resource
type SecretDataKey struct {
UID string
Active bool
Namespace string
Label string
Provider encryption.ProviderID
EncryptedData []byte
Created time.Time
Updated time.Time
}
// DataKeyStorage is the interface for wiring and dependency injection.
type DataKeyStorage interface {
CreateDataKey(ctx context.Context, dataKey *SecretDataKey) error
GetDataKey(ctx context.Context, namespace, uid string) (*SecretDataKey, error)
GetCurrentDataKey(ctx context.Context, namespace, label string) (*SecretDataKey, error)
GetAllDataKeys(ctx context.Context, namespace string) ([]*SecretDataKey, error)
DisableDataKeys(ctx context.Context, namespace string) error
DeleteDataKey(ctx context.Context, namespace, uid string) error
}
@@ -1,3 +1,26 @@
package encryption
import (
"fmt"
"strings"
"time"
)
const UsageInsightsPrefix = "secrets_manager"
type ProviderID string
func (id ProviderID) Kind() (string, error) {
idStr := string(id)
parts := strings.SplitN(idStr, ".", 2)
if len(parts) != 2 {
return "", fmt.Errorf("malformatted provider identifier %s: expected format <provider>.<keyName>", idStr)
}
return parts[0], nil
}
func KeyLabel(providerID ProviderID) string {
return fmt.Sprintf("%s@%s", time.Now().Format("2006-01-02"), providerID)
}
+415
View File
@@ -0,0 +1,415 @@
package server
import (
"context"
"errors"
"fmt"
"math/rand"
"net"
"net/http"
"strconv"
"sync"
"testing"
"time"
claims "github.com/grafana/authlib/types"
"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/modules"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/sqlstore/sqlutil"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/storage/unified/resource"
"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
"github.com/grafana/grafana/pkg/storage/unified/search"
"github.com/grafana/grafana/pkg/storage/unified/sql"
"github.com/prometheus/client_golang/prometheus"
"github.com/stretchr/testify/require"
"go.opentelemetry.io/otel/trace/noop"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
"google.golang.org/grpc/health/grpc_health_v1"
"google.golang.org/grpc/metadata"
"k8s.io/component-base/metrics/legacyregistry"
)
var (
testIndexFileThreshold = 200 // just needs to be bigger than max playlist number, so the indexer don't use the filesystem
namespaceCount = 250 // how many stacks we're simulating
maxPlaylistPerNamespace = 50 // upper bound on how many playlists we will seed to each stack.
)
//nolint:gocyclo
func TestIntegrationDistributor(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
dbType := sqlutil.GetTestDBType()
if dbType != "mysql" {
t.Skip()
}
// this next line is to avoid double registration when registering sprinkles metrics
legacyregistry.Registerer = func() prometheus.Registerer { return prometheus.NewRegistry() }
db, err := sqlutil.GetTestDB(dbType)
require.NoError(t, err)
testNamespaces := make([]string, 0, namespaceCount)
for i := range namespaceCount {
testNamespaces = append(testNamespaces, "stacks-"+strconv.Itoa(i))
}
baselineServer := createBaselineServer(t, dbType, db.ConnStr, testNamespaces)
testServers := make([]testModuleServer, 0, 2)
memberlistPort := getRandomPort()
distributorServer := initDistributorServerForTest(t, memberlistPort)
testServers = append(testServers, createStorageServerApi(t, 1, dbType, db.ConnStr, memberlistPort))
testServers = append(testServers, createStorageServerApi(t, 2, dbType, db.ConnStr, memberlistPort))
startAndWaitHealthy(t, distributorServer)
for _, testServer := range testServers {
startAndWaitHealthy(t, testServer)
}
t.Run("should expose ring endpoint", func(t *testing.T) {
client := http.Client{}
res, err := client.Get(fmt.Sprintf("http://localhost:%s/ring", distributorServer.httpPort))
require.NoError(t, err)
require.Equal(t, res.StatusCode, http.StatusOK)
_ = res.Body.Close()
})
t.Run("should expose memberlist endpoint", func(t *testing.T) {
client := http.Client{}
res, err := client.Get(fmt.Sprintf("http://localhost:%s/memberlist", distributorServer.httpPort))
require.NoError(t, err)
require.Equal(t, res.StatusCode, http.StatusOK)
_ = res.Body.Close()
})
t.Run("GetStats", func(t *testing.T) {
instanceResponseCount := make(map[string]int)
for _, ns := range testNamespaces {
req := &resourcepb.ResourceStatsRequest{
Namespace: ns,
}
baselineRes := getBaselineResponse(t, req, baselineServer.GetStats)
distributorRes := getDistributorResponse(t, req, distributorServer.resourceClient.GetStats, instanceResponseCount)
require.Equal(t, baselineRes.String(), distributorRes.String())
}
for instance, count := range instanceResponseCount {
require.GreaterOrEqual(t, count, 1, "instance did not get any traffic: "+instance)
}
})
t.Run("CountManagedObjects", func(t *testing.T) {
instanceResponseCount := make(map[string]int)
for _, ns := range testNamespaces {
req := &resourcepb.CountManagedObjectsRequest{
Namespace: ns,
}
baselineRes := getBaselineResponse(t, req, baselineServer.CountManagedObjects)
distributorRes := getDistributorResponse(t, req, distributorServer.resourceClient.CountManagedObjects, instanceResponseCount)
require.Equal(t, baselineRes.String(), distributorRes.String())
}
for instance, count := range instanceResponseCount {
require.GreaterOrEqual(t, count, 1, "instance did not get any traffic: "+instance)
}
})
t.Run("ListManagedObjects", func(t *testing.T) {
instanceResponseCount := make(map[string]int)
for _, ns := range testNamespaces {
req := &resourcepb.ListManagedObjectsRequest{
Namespace: ns,
}
baselineRes := getBaselineResponse(t, req, baselineServer.ListManagedObjects)
distributorRes := getDistributorResponse(t, req, distributorServer.resourceClient.ListManagedObjects, instanceResponseCount)
require.Equal(t, baselineRes.String(), distributorRes.String())
}
for instance, count := range instanceResponseCount {
require.GreaterOrEqual(t, count, 1, "instance did not get any traffic: "+instance)
}
})
t.Run("Search", func(t *testing.T) {
instanceResponseCount := make(map[string]int)
for _, ns := range testNamespaces {
req := &resourcepb.ResourceSearchRequest{
Options: &resourcepb.ListOptions{
Key: &resourcepb.ResourceKey{
Group: "playlist.grafana.app",
Resource: "aoeuaeou",
Namespace: ns,
},
},
}
baselineRes := getBaselineResponse(t, req, baselineServer.Search)
distributorRes := getDistributorResponse(t, req, distributorServer.resourceClient.Search, instanceResponseCount)
// sometimes the querycost is different between the two. Happens randomly and we don't have control over it
// as it comes from bleve. Since we are not testing search functionality we hard-set this to 0 to avoid
// flaky tests
distributorRes.QueryCost = 0
baselineRes.QueryCost = 0
require.Equal(t, baselineRes.String(), distributorRes.String())
}
for instance, count := range instanceResponseCount {
require.GreaterOrEqual(t, count, 1, "instance did not get any traffic: "+instance)
}
})
var wg sync.WaitGroup
for _, testServer := range testServers {
wg.Add(1)
go func() {
defer wg.Done()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := testServer.server.Shutdown(ctx, "tests are done"); err != nil {
require.NoError(t, err)
}
}()
}
wg.Wait()
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
if err := distributorServer.server.Shutdown(ctx, "tests are done"); err != nil {
require.NoError(t, err)
}
}
func getBaselineResponse[Req any, Resp any](t *testing.T, req *Req, fn func(ctx context.Context, req *Req) (*Resp, error)) *Resp {
ctx := context.Background()
baselineRes, err := fn(ctx, req)
require.NoError(t, err)
return baselineRes
}
func getDistributorResponse[Req any, Resp any](t *testing.T, req *Req, fn func(ctx context.Context, req *Req, opts ...grpc.CallOption) (*Resp, error), instanceResponseCount map[string]int) *Resp {
ctx := identity.WithServiceIdentityContext(context.Background(), 1)
var header metadata.MD
res, err := fn(ctx, req, grpc.Header(&header))
require.NoError(t, err)
instance := header.Get("proxied-instance-id")
if len(instance) != 1 {
t.Fatal("received invalid proxied-instance-id header", instance)
}
instanceResponseCount[instance[0]] += 1
return res
}
func startAndWaitHealthy(t *testing.T, testServer testModuleServer) {
go func() {
// this next line is to avoid double registration, as both InitializeDocumentBuilders as well as ProvideUnifiedStorageGrpcService
// are hard-coded to use prometheus.DefaultRegisterer
// the alternative would be to get the registry from wire, in which case the tests would receive a new
// registry automatically, but that _may_ change metric names
// We can remove this once that's fixed
prometheus.DefaultRegisterer = prometheus.NewRegistry()
if err := testServer.server.Run(); err != nil && !errors.Is(err, context.Canceled) {
require.NoError(t, err)
}
}()
deadline := time.Now().Add(20 * time.Second)
for {
conn, err := net.DialTimeout("tcp", testServer.grpcAddress, 1*time.Second)
if err == nil {
_ = conn.Close()
break
}
if time.Now().After(deadline) {
t.Fatal("server failed to become ready: ", testServer.id)
}
time.Sleep(1 * time.Second)
}
res, err := testServer.healthClient.Check(context.Background(), &grpc_health_v1.HealthCheckRequest{})
require.NoError(t, err)
require.Equal(t, res.Status, grpc_health_v1.HealthCheckResponse_SERVING)
}
type testModuleServer struct {
server *ModuleServer
healthClient grpc_health_v1.HealthClient
resourceClient resource.ResourceClient
id string
grpcAddress string
httpPort string
}
func getRandomPort() int {
ln, _ := net.Listen("tcp", "127.0.0.1:0")
_ = ln.Close()
return ln.Addr().(*net.TCPAddr).Port
}
func initDistributorServerForTest(t *testing.T, memberlistPort int) testModuleServer {
cfg := setting.NewCfg()
cfg.HTTPPort = strconv.Itoa(getRandomPort())
cfg.GRPCServer.Network = "tcp"
cfg.GRPCServer.Address = "127.0.0.1:" + strconv.Itoa(getRandomPort())
cfg.EnableSharding = true
cfg.MemberlistBindAddr = "127.0.0.1"
cfg.MemberlistJoinMember = "127.0.0.1:" + strconv.Itoa(memberlistPort)
cfg.MemberlistAdvertiseAddr = "127.0.0.1"
cfg.MemberlistAdvertisePort = memberlistPort
cfg.Target = []string{modules.Distributor}
cfg.InstanceID = "distributor" // does nothing for the distributor but may be useful to debug tests
conn, err := grpc.NewClient(cfg.GRPCServer.Address,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
require.NoError(t, err)
client := resource.NewLegacyResourceClient(conn, conn)
server := initModuleServerForTest(t, cfg, Options{}, api.ServerOptions{})
server.resourceClient = client
return server
}
func createStorageServerApi(t *testing.T, instanceId int, dbType, dbConnStr string, memberlistPort int) testModuleServer {
cfg := setting.NewCfg()
section, err := cfg.Raw.NewSection("database")
require.NoError(t, err)
_, err = section.NewKey("type", dbType)
require.NoError(t, err)
_, err = section.NewKey("connection_string", dbConnStr)
require.NoError(t, err)
cfg.HTTPPort = strconv.Itoa(getRandomPort())
cfg.GRPCServer.Network = "tcp"
cfg.GRPCServer.Address = "127.0.0.1:" + strconv.Itoa(getRandomPort())
cfg.EnableSharding = true
cfg.MemberlistBindAddr = "127.0.0.1"
cfg.MemberlistJoinMember = "127.0.0.1:" + strconv.Itoa(memberlistPort)
cfg.MemberlistAdvertiseAddr = "127.0.0.1"
cfg.MemberlistAdvertisePort = getRandomPort()
cfg.InstanceID = "instance-" + strconv.Itoa(instanceId)
cfg.IndexPath = t.TempDir() + cfg.InstanceID
cfg.IndexFileThreshold = testIndexFileThreshold
cfg.Target = []string{modules.StorageServer}
return initModuleServerForTest(t, cfg, Options{}, api.ServerOptions{})
}
func initModuleServerForTest(
t *testing.T,
cfg *setting.Cfg,
opts Options,
apiOpts api.ServerOptions,
) testModuleServer {
ms, err := NewModule(opts, apiOpts, featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearch), cfg, nil, nil, prometheus.NewRegistry(), prometheus.DefaultGatherer, nil)
require.NoError(t, err)
conn, err := grpc.NewClient(cfg.GRPCServer.Address,
grpc.WithTransportCredentials(insecure.NewCredentials()),
)
require.NoError(t, err)
healthClient := grpc_health_v1.NewHealthClient(conn)
return testModuleServer{server: ms, grpcAddress: cfg.GRPCServer.Address, httpPort: cfg.HTTPPort, healthClient: healthClient, id: cfg.InstanceID}
}
func createBaselineServer(t *testing.T, dbType, dbConnStr string, testNamespaces []string) resource.ResourceServer {
cfg := setting.NewCfg()
section, err := cfg.Raw.NewSection("database")
require.NoError(t, err)
_, err = section.NewKey("type", dbType)
require.NoError(t, err)
_, err = section.NewKey("connection_string", dbConnStr)
require.NoError(t, err)
cfg.IndexPath = t.TempDir()
cfg.IndexFileThreshold = testIndexFileThreshold
features := featuremgmt.WithFeatures(featuremgmt.FlagUnifiedStorageSearch)
docBuilders, err := InitializeDocumentBuilders(cfg)
require.NoError(t, err)
tracer := noop.NewTracerProvider().Tracer("test-tracer")
require.NoError(t, err)
searchOpts, err := search.NewSearchOptions(features, cfg, tracer, docBuilders, nil)
require.NoError(t, err)
server, err := sql.NewResourceServer(nil, cfg, tracer, nil, nil, searchOpts, nil, nil, features)
require.NoError(t, err)
testUserA := &identity.StaticRequester{
Type: claims.TypeUser,
Login: "testuser",
UserID: 123,
UserUID: "u123",
OrgRole: identity.RoleAdmin,
IsGrafanaAdmin: true, // can do anything
}
ctx := claims.WithAuthInfo(context.Background(), testUserA)
for _, ns := range testNamespaces {
for range rand.Intn(maxPlaylistPerNamespace) + 1 {
_, err = server.Create(ctx, generatePlaylistPayload(ns))
require.NoError(t, err)
}
}
return server
}
var counter int
func generatePlaylistPayload(ns string) *resourcepb.CreateRequest {
name := "playlist" + strconv.Itoa(counter)
counter += 1
return &resourcepb.CreateRequest{
Value: []byte(fmt.Sprintf(`{
"apiVersion": "playlist.grafana.app/v0alpha1",
"kind": "Playlist",
"metadata": {
"name": "%s",
"uid": "xyz",
"namespace": "%s",
"annotations": {
"grafana.app/repoName": "elsewhere",
"grafana.app/repoPath": "path/to/item",
"grafana.app/repoTimestamp": "2024-02-02T00:00:00Z"
}
},
"spec": {
"title": "hello",
"interval": "5m",
"items": [
{
"type": "dashboard_by_uid",
"value": "vmie2cmWz"
}
]
}
}`, name, ns)),
Key: &resourcepb.ResourceKey{
Group: "playlist.grafana.app",
Resource: "aoeuaeou",
Namespace: ns,
Name: name,
},
}
}
+2
View File
@@ -51,6 +51,8 @@ func toMemberlistConfig(cfg *setting.Cfg) *memberlist.KVConfig {
if cfg.MemberlistAdvertiseAddr != "" {
memberlistKVcfg.AdvertiseAddr = cfg.MemberlistAdvertiseAddr
}
memberlistKVcfg.AdvertisePort = cfg.MemberlistAdvertisePort
memberlistKVcfg.TCPTransport.BindPort = cfg.MemberlistAdvertisePort
memberlistKVcfg.JoinMembers = []string{cfg.MemberlistJoinMember}
return memberlistKVcfg
+1
View File
@@ -423,6 +423,7 @@ var wireBasicSet = wire.NewSet(
secretmetadata.ProvideSecureValueMetadataStorage,
secretmetadata.ProvideKeeperMetadataStorage,
secretmetadata.ProvideOutboxQueue,
secretencryption.ProvideDataKeyStorage,
secretencryption.ProvideEncryptedValueStorage,
secretmigrator.NewWithEngine,
secretdatabase.ProvideDatabase,
@@ -49,7 +49,7 @@ func setupTestEnv(t testing.TB) *Service {
return ac
}
func TestUsageMetrics(t *testing.T) {
func TestIntegrationUsageMetrics(t *testing.T) {
tests := []struct {
name string
expectedValue int
@@ -80,7 +80,7 @@ func TestUsageMetrics(t *testing.T) {
}
}
func TestService_DeclareFixedRoles(t *testing.T) {
func TestIntegrationService_DeclareFixedRoles(t *testing.T) {
tests := []struct {
name string
registrations []accesscontrol.RoleRegistration
@@ -165,7 +165,7 @@ func TestService_DeclareFixedRoles(t *testing.T) {
}
}
func TestService_DeclarePluginRoles(t *testing.T) {
func TestIntegrationService_DeclarePluginRoles(t *testing.T) {
tests := []struct {
name string
pluginID string
@@ -278,7 +278,7 @@ func TestService_DeclarePluginRoles(t *testing.T) {
}
}
func TestService_RegisterFixedRoles(t *testing.T) {
func TestIntegrationService_RegisterFixedRoles(t *testing.T) {
tests := []struct {
name string
token licensing.Licensing
@@ -380,7 +380,7 @@ func TestService_RegisterFixedRoles(t *testing.T) {
}
}
func TestService_SearchUsersPermissions(t *testing.T) {
func TestIntegrationService_SearchUsersPermissions(t *testing.T) {
searchOption := accesscontrol.SearchOptions{ActionPrefix: "teams"}
ctx := context.Background()
listAllPerms := map[string][]string{accesscontrol.ActionUsersPermissionsRead: {"users:*"}}
@@ -601,7 +601,7 @@ func TestService_SearchUsersPermissions(t *testing.T) {
}
}
func TestService_SearchUserPermissions(t *testing.T) {
func TestIntegrationService_SearchUserPermissions(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
@@ -832,7 +832,7 @@ func TestService_SearchUserPermissions(t *testing.T) {
}
}
func TestService_SaveExternalServiceRole(t *testing.T) {
func TestIntegrationService_SaveExternalServiceRole(t *testing.T) {
type run struct {
cmd accesscontrol.SaveExternalServiceRoleCommand
wantErr bool
@@ -918,7 +918,7 @@ func TestService_SaveExternalServiceRole(t *testing.T) {
}
}
func TestService_DeleteExternalServiceRole(t *testing.T) {
func TestIntegrationService_DeleteExternalServiceRole(t *testing.T) {
tests := []struct {
name string
initCmd *accesscontrol.SaveExternalServiceRoleCommand
@@ -971,7 +971,7 @@ func TestService_DeleteExternalServiceRole(t *testing.T) {
}
}
func TestService_GetRoleByName(t *testing.T) {
func TestIntegrationService_GetRoleByName(t *testing.T) {
t.Parallel()
ctx := context.Background()
@@ -44,7 +44,7 @@ type getUserPermissionsTestCase struct {
policyCount int
}
func TestAccessControlStore_GetUserPermissions(t *testing.T) {
func TestIntegrationAccessControlStore_GetUserPermissions(t *testing.T) {
tests := []getUserPermissionsTestCase{
{
desc: "should successfully get user, team and builtin permissions",
@@ -158,7 +158,7 @@ type getTeamsPermissionsTestCase struct {
expected int
}
func TestAccessControlStore_GetTeamsPermissions(t *testing.T) {
func TestIntegrationAccessControlStore_GetTeamsPermissions(t *testing.T) {
tests := []getTeamsPermissionsTestCase{
{
desc: "should successfully get team permissions",
@@ -230,7 +230,7 @@ func TestAccessControlStore_GetTeamsPermissions(t *testing.T) {
}
}
func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
func TestIntegrationAccessControlStore_DeleteUserPermissions(t *testing.T) {
t.Run("expect permissions in all orgs to be deleted", func(t *testing.T) {
store, permissionsStore, usrSvc, teamSvc, _, sql := setupTestEnv(t)
user, _ := createUserAndTeam(t, sql, usrSvc, teamSvc, 1)
@@ -312,7 +312,7 @@ func TestAccessControlStore_DeleteUserPermissions(t *testing.T) {
})
}
func TestAccessControlStore_DeleteTeamPermissions(t *testing.T) {
func TestIntegrationAccessControlStore_DeleteTeamPermissions(t *testing.T) {
t.Run("expect permissions related to team to be deleted", func(t *testing.T) {
store, permissionsStore, usrSvc, teamSvc, _, sql := setupTestEnv(t)
user, team := createUserAndTeam(t, sql, usrSvc, teamSvc, 1)
@@ -767,7 +767,7 @@ func TestIntegrationAccessControlStore_SearchUsersPermissions(t *testing.T) {
}
}
func TestAccessControlStore_GetUsersBasicRoles(t *testing.T) {
func TestIntegrationAccessControlStore_GetUsersBasicRoles(t *testing.T) {
ctx := context.Background()
tests := []struct {
name string
@@ -12,7 +12,7 @@ import (
"github.com/stretchr/testify/require"
)
func TestAccessControlStore_SaveExternalServiceRole(t *testing.T) {
func TestIntegrationAccessControlStore_SaveExternalServiceRole(t *testing.T) {
type run struct {
cmd accesscontrol.SaveExternalServiceRoleCommand
wantErr bool
@@ -152,7 +152,7 @@ func TestAccessControlStore_SaveExternalServiceRole(t *testing.T) {
}
}
func TestAccessControlStore_DeleteExternalServiceRole(t *testing.T) {
func TestIntegrationAccessControlStore_DeleteExternalServiceRole(t *testing.T) {
extID := "app1"
tests := []struct {
name string
+1 -1
View File
@@ -32,7 +32,7 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestFilter_Datasources(t *testing.T) {
func TestIntegrationFilter_Datasources(t *testing.T) {
tests := []filterDatasourcesTestCase{
{
desc: "expect all data sources to be returned",
@@ -30,7 +30,7 @@ type getDescriptionTestCase struct {
expectedStatus int
}
func TestApi_getDescription(t *testing.T) {
func TestIntegrationApi_getDescription(t *testing.T) {
tests := []getDescriptionTestCase{
{
desc: "should return description",
@@ -135,7 +135,7 @@ type getPermissionsTestCase struct {
expectedStatus int
}
func TestApi_getPermissions(t *testing.T) {
func TestIntegrationApi_getPermissions(t *testing.T) {
tests := []getPermissionsTestCase{
{
desc: "expect permissions for resource with id 1",
@@ -181,7 +181,7 @@ type setBuiltinPermissionTestCase struct {
permissions []accesscontrol.Permission
}
func TestApi_setBuiltinRolePermission(t *testing.T) {
func TestIntegrationApi_setBuiltinRolePermission(t *testing.T) {
tests := []setBuiltinPermissionTestCase{
{
desc: "should set Edit permission for Viewer",
@@ -260,7 +260,7 @@ type setTeamPermissionTestCase struct {
byUID bool
}
func TestApi_setTeamPermission(t *testing.T) {
func TestIntegrationApi_setTeamPermission(t *testing.T) {
tests := []setTeamPermissionTestCase{
{
desc: "should set Edit permission for team 1",
@@ -367,7 +367,7 @@ type setUserPermissionTestCase struct {
permissions []accesscontrol.Permission
}
func TestApi_setUserPermission(t *testing.T) {
func TestIntegrationApi_setUserPermission(t *testing.T) {
tests := []setUserPermissionTestCase{
{
desc: "should set Edit permission for user 1",
@@ -442,7 +442,7 @@ func TestApi_setUserPermission(t *testing.T) {
}
}
func TestApi_setUserPermissionForTeams(t *testing.T) {
func TestIntegrationApi_setUserPermissionForTeams(t *testing.T) {
type setUserPermissionForTeamsTestCase struct {
setUserPermissionTestCase
teamCmd *team.CreateTeamCommand
@@ -31,7 +31,7 @@ type setUserPermissionTest struct {
callHook bool
}
func TestService_SetUserPermission(t *testing.T) {
func TestIntegrationService_SetUserPermission(t *testing.T) {
tests := []setUserPermissionTest{
{
desc: "should call hook when updating user permissions",
@@ -75,7 +75,7 @@ type setTeamPermissionTest struct {
callHook bool
}
func TestService_SetTeamPermission(t *testing.T) {
func TestIntegrationService_SetTeamPermission(t *testing.T) {
tests := []setTeamPermissionTest{
{
desc: "should call hook when updating user permissions",
@@ -124,7 +124,7 @@ type setBuiltInRolePermissionTest struct {
callHook bool
}
func TestService_SetBuiltInRolePermission(t *testing.T) {
func TestIntegrationService_SetBuiltInRolePermission(t *testing.T) {
tests := []setBuiltInRolePermissionTest{
{
desc: "should call hook when updating user permissions",
@@ -166,7 +166,7 @@ type setPermissionsTest struct {
expectErr bool
}
func TestService_SetPermissions(t *testing.T) {
func TestIntegrationService_SetPermissions(t *testing.T) {
tests := []setPermissionsTest{
{
desc: "should set all permissions",
@@ -235,7 +235,7 @@ func TestService_SetPermissions(t *testing.T) {
}
}
func TestService_RegisterActionSets(t *testing.T) {
func TestIntegrationService_RegisterActionSets(t *testing.T) {
type registerActionSetsTest struct {
desc string
options Options
@@ -823,7 +823,7 @@ func TestIntegrationTokenCount(t *testing.T) {
require.Equal(t, int64(0), count)
}
func TestRevokeAllUserTokens(t *testing.T) {
func TestIntegrationRevokeAllUserTokens(t *testing.T) {
t.Run("should not fail if the external sessions could not be removed", func(t *testing.T) {
ctx := createTestContext(t)
usr := &user.User{ID: int64(10)}
@@ -856,7 +856,7 @@ func TestRevokeAllUserTokens(t *testing.T) {
})
}
func TestRevokeToken(t *testing.T) {
func TestIntegrationRevokeToken(t *testing.T) {
t.Run("should not fail if the external sessions could not be removed", func(t *testing.T) {
ctx := createTestContext(t)
usr := &user.User{ID: int64(10)}
@@ -887,7 +887,7 @@ func TestRevokeToken(t *testing.T) {
})
}
func TestBatchRevokeAllUserTokens(t *testing.T) {
func TestIntegrationBatchRevokeAllUserTokens(t *testing.T) {
t.Run("should not fail if the external sessions could not be removed", func(t *testing.T) {
ctx := createTestContext(t)
userIds := []int64{1, 2, 3}
+7 -7
View File
@@ -44,7 +44,7 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestVerifyUsingPKIXPublicKeyFile(t *testing.T) {
func TestIntegrationVerifyUsingPKIXPublicKeyFile(t *testing.T) {
key := rsaKeys[0]
unknownKey := rsaKeys[1]
@@ -79,7 +79,7 @@ func TestVerifyUsingPKIXPublicKeyFile(t *testing.T) {
})
}
func TestVerifyUsingJWKSetFile(t *testing.T) {
func TestIntegrationVerifyUsingJWKSetFile(t *testing.T) {
configure := func(t *testing.T, cfg *setting.Cfg) {
t.Helper()
@@ -118,7 +118,7 @@ func TestVerifyUsingJWKSetFile(t *testing.T) {
}, configure)
}
func TestVerifyUsingJWKSetURL(t *testing.T) {
func TestIntegrationVerifyUsingJWKSetURL(t *testing.T) {
t.Run("should refuse to start with non-https URL", func(t *testing.T) {
var err error
@@ -160,7 +160,7 @@ func TestVerifyUsingJWKSetURL(t *testing.T) {
})
}
func TestCachingJWKHTTPResponse(t *testing.T) {
func TestIntegrationCachingJWKHTTPResponse(t *testing.T) {
jwkCachingScenario(t, "caches the jwk response", func(t *testing.T, sc cachingScenarioContext) {
for i := 0; i < 5; i++ {
token := sign(t, &jwKeys[0], jwt.Claims{Subject: subject}, nil)
@@ -200,7 +200,7 @@ func TestCachingJWKHTTPResponse(t *testing.T) {
})
}
func TestSignatureWithNoneAlgorithm(t *testing.T) {
func TestIntegrationSignatureWithNoneAlgorithm(t *testing.T) {
scenario(t, "rejects a token signed with \"none\" algorithm", func(t *testing.T, sc scenarioContext) {
token := signNone(t, jwt.Claims{Subject: "foo"})
_, err := sc.authJWTSvc.Verify(sc.ctx, token)
@@ -208,7 +208,7 @@ func TestSignatureWithNoneAlgorithm(t *testing.T) {
}, configurePKIXPublicKeyFile)
}
func TestClaimValidation(t *testing.T) {
func TestIntegrationClaimValidation(t *testing.T) {
key := rsaKeys[0]
scenario(t, "validates iss field for equality", func(t *testing.T, sc scenarioContext) {
@@ -368,7 +368,7 @@ func jwkCachingScenario(t *testing.T, desc string, fn cachingScenarioFunc, cbs .
})
}
func TestBase64Paddings(t *testing.T) {
func TestIntegrationBase64Paddings(t *testing.T) {
key := rsaKeys[0]
scenario(t, "verifies a token with base64 padding (non compliant rfc7515#section-2 but accepted)", func(t *testing.T, sc scenarioContext) {
@@ -2643,7 +2643,7 @@ func TestCleanUpDashboard(t *testing.T) {
}
}
func TestK8sDashboardCleanupJob(t *testing.T) {
func TestIntegrationK8sDashboardCleanupJob(t *testing.T) {
tests := []struct {
name string
featureEnabled bool
@@ -38,7 +38,7 @@ func TestMain(m *testing.M) {
testsuite.Run(m)
}
func TestDashboardSnapshotsService(t *testing.T) {
func TestIntegrationDashboardSnapshotsService(t *testing.T) {
sqlStore := db.InitTestDB(t)
cfg := setting.NewCfg()
dsStore := dashsnapdb.ProvideStore(sqlStore, cfg)
@@ -97,7 +97,7 @@ func TestDashboardSnapshotsService(t *testing.T) {
})
}
func TestValidateDashboardExists(t *testing.T) {
func TestIntegrationValidateDashboardExists(t *testing.T) {
sqlStore := db.InitTestDB(t)
cfg := setting.NewCfg()
dsStore := dashsnapdb.ProvideStore(sqlStore, cfg)
@@ -63,7 +63,7 @@ func (d *dataSourceMockRetriever) GetDataSource(ctx context.Context, query *data
return nil, datasources.ErrDataSourceNotFound
}
func TestService_AddDataSource(t *testing.T) {
func TestIntegrationService_AddDataSource(t *testing.T) {
t.Run("should not fail if the plugin is not installed", func(t *testing.T) {
dsService := initDSService(t)
dsService.pluginStore = &pluginstore.FakePluginStore{
@@ -354,7 +354,7 @@ func TestService_getAvailableName(t *testing.T) {
}
}
func TestService_UpdateDataSource(t *testing.T) {
func TestIntegrationService_UpdateDataSource(t *testing.T) {
t.Run("should return not found error if datasource not found", func(t *testing.T) {
dsService := initDSService(t)
@@ -761,7 +761,7 @@ func TestService_UpdateDataSource(t *testing.T) {
})
}
func TestService_DeleteDataSource(t *testing.T) {
func TestIntegrationService_DeleteDataSource(t *testing.T) {
t.Run("should not return an error if data source doesn't exist", func(t *testing.T) {
sqlStore := db.InitTestDB(t)
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
@@ -1038,7 +1038,7 @@ func TestService_awsServiceNamespace(t *testing.T) {
}
//nolint:goconst
func TestService_GetHttpTransport(t *testing.T) {
func TestIntegrationService_GetHttpTransport(t *testing.T) {
cfg := &setting.Cfg{}
t.Run("Should use cached proxy", func(t *testing.T) {
@@ -1450,7 +1450,7 @@ func TestService_GetHttpTransport(t *testing.T) {
})
}
func TestService_getProxySettings(t *testing.T) {
func TestIntegrationService_getProxySettings(t *testing.T) {
sqlStore := db.InitTestDB(t)
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
@@ -1528,7 +1528,7 @@ func TestService_getProxySettings(t *testing.T) {
})
}
func TestService_getTimeout(t *testing.T) {
func TestIntegrationService_getTimeout(t *testing.T) {
cfg := &setting.Cfg{}
originalTimeout := sdkhttpclient.DefaultTimeoutOptions.Timeout
sdkhttpclient.DefaultTimeoutOptions.Timeout = time.Minute
@@ -1562,7 +1562,7 @@ func TestService_getTimeout(t *testing.T) {
}
}
func TestService_GetDecryptedValues(t *testing.T) {
func TestIntegrationService_GetDecryptedValues(t *testing.T) {
t.Run("should migrate and retrieve values from secure json data", func(t *testing.T) {
ds := &datasources.DataSource{
ID: 1,
@@ -1621,7 +1621,7 @@ func TestService_GetDecryptedValues(t *testing.T) {
})
}
func TestDataSource_CustomHeaders(t *testing.T) {
func TestIntegrationDataSource_CustomHeaders(t *testing.T) {
sqlStore := db.InitTestDB(t)
secretsService := secretsmng.SetupTestService(t, fakes.NewFakeSecretsStore())
secretsStore := secretskvs.NewSQLSecretsKVStore(sqlStore, secretsService, log.New("test.logger"))
+10 -22
View File
@@ -163,13 +163,6 @@ var (
Expression: "true", // turned on by default
AllowSelfServe: true,
},
{
Name: "lokiQuerySplittingConfig",
Description: "Give users the option to configure split durations for Loki queries",
Stage: FeatureStageExperimental,
FrontendOnly: true,
Owner: grafanaObservabilityLogsSquad,
},
{
Name: "individualCookiePreferences",
Description: "Support overriding cookie preferences per user",
@@ -262,13 +255,6 @@ var (
Stage: FeatureStageExperimental,
Owner: grafanaFrontendPlatformSquad,
},
{
Name: "lokiPredefinedOperations",
Description: "Adds predefined query operations to Loki query editor",
FrontendOnly: true,
Stage: FeatureStageExperimental,
Owner: grafanaObservabilityLogsSquad,
},
{
Name: "pluginsFrontendSandbox",
Description: "Enables the plugins frontend sandbox",
@@ -570,14 +556,6 @@ var (
Stage: FeatureStagePublicPreview,
Owner: awsDatasourcesSquad,
},
{
Name: "lokiStructuredMetadata",
Description: "Enables the loki data source to request structured metadata from the Loki server",
Stage: FeatureStageGeneralAvailability,
FrontendOnly: false,
Owner: grafanaObservabilityLogsSquad,
Expression: "true",
},
{
Name: "cachingOptimizeSerializationMemoryUsage",
Description: "If enabled, the caching backend gradually serializes query responses for the cache, comparing against the configured `[caching]max_value_mb` value as it goes. This can can help prevent Grafana from running out of memory while attempting to cache very large query responses.",
@@ -1771,6 +1749,16 @@ var (
Owner: grafanaPartnerPluginsSquad,
Expression: "false",
},
{
Name: "enableAppChromeExtensions",
Description: "Set this to true to enable all app chrome extensions registered by plugins.",
Stage: FeatureStageExperimental,
Owner: grafanaPluginsPlatformSquad,
HideFromAdminPage: true,
HideFromDocs: true,
FrontendOnly: true,
Expression: "false", // extensions will be disabled by default
},
}
)
+1 -3
View File
@@ -19,7 +19,6 @@ editPanelCSVDragAndDrop,experimental,@grafana/dataviz-squad,false,false,true
logsContextDatasourceUi,GA,@grafana/observability-logs,false,false,true
lokiShardSplitting,experimental,@grafana/observability-logs,false,false,true
lokiQuerySplitting,GA,@grafana/observability-logs,false,false,true
lokiQuerySplittingConfig,experimental,@grafana/observability-logs,false,false,true
individualCookiePreferences,experimental,@grafana/grafana-backend-group,false,false,false
influxdbBackendMigration,GA,@grafana/partner-datasources,false,false,true
influxqlStreamingParser,experimental,@grafana/partner-datasources,false,false,false
@@ -33,7 +32,6 @@ refactorVariablesTimeRange,preview,@grafana/dashboards-squad,false,false,false
faroDatasourceSelector,preview,@grafana/app-o11y,false,false,true
enableDatagridEditing,preview,@grafana/dataviz-squad,false,false,true
extraThemes,experimental,@grafana/grafana-frontend-platform,false,false,true
lokiPredefinedOperations,experimental,@grafana/observability-logs,false,false,true
pluginsFrontendSandbox,privatePreview,@grafana/plugins-platform-backend,false,false,false
pluginsDetailsRightPanel,GA,@grafana/plugins-platform-backend,false,false,true
sqlDatasourceDatabaseSelection,preview,@grafana/oss-big-tent,false,false,true
@@ -75,7 +73,6 @@ queryServiceRewrite,experimental,@grafana/grafana-datasources-core-services,fals
queryServiceFromUI,experimental,@grafana/grafana-datasources-core-services,false,false,true
queryServiceFromExplore,experimental,@grafana/grafana-datasources-core-services,false,false,true
cloudWatchBatchQueries,preview,@grafana/aws-datasources,false,false,false
lokiStructuredMetadata,GA,@grafana/observability-logs,false,false,false
cachingOptimizeSerializationMemoryUsage,experimental,@grafana/grafana-operator-experience-squad,false,false,false
prometheusCodeModeMetricNamesSearch,experimental,@grafana/oss-big-tent,false,false,true
addFieldFromCalculationStatFunctions,GA,@grafana/dataviz-squad,false,false,true
@@ -231,3 +228,4 @@ alertingImportAlertmanagerAPI,experimental,@grafana/alerting-squad,false,false,f
preferLibraryPanelTitle,privatePreview,@grafana/dashboards-squad,false,false,false
tabularNumbers,GA,@grafana/grafana-frontend-platform,false,false,false
newInfluxDSConfigPageDesign,privatePreview,@grafana/partner-datasources,false,false,false
enableAppChromeExtensions,experimental,@grafana/plugins-platform-backend,false,false,true
1 Name Stage Owner requiresDevMode RequiresRestart FrontendOnly
19 logsContextDatasourceUi GA @grafana/observability-logs false false true
20 lokiShardSplitting experimental @grafana/observability-logs false false true
21 lokiQuerySplitting GA @grafana/observability-logs false false true
lokiQuerySplittingConfig experimental @grafana/observability-logs false false true
22 individualCookiePreferences experimental @grafana/grafana-backend-group false false false
23 influxdbBackendMigration GA @grafana/partner-datasources false false true
24 influxqlStreamingParser experimental @grafana/partner-datasources false false false
32 faroDatasourceSelector preview @grafana/app-o11y false false true
33 enableDatagridEditing preview @grafana/dataviz-squad false false true
34 extraThemes experimental @grafana/grafana-frontend-platform false false true
lokiPredefinedOperations experimental @grafana/observability-logs false false true
35 pluginsFrontendSandbox privatePreview @grafana/plugins-platform-backend false false false
36 pluginsDetailsRightPanel GA @grafana/plugins-platform-backend false false true
37 sqlDatasourceDatabaseSelection preview @grafana/oss-big-tent false false true
73 queryServiceFromUI experimental @grafana/grafana-datasources-core-services false false true
74 queryServiceFromExplore experimental @grafana/grafana-datasources-core-services false false true
75 cloudWatchBatchQueries preview @grafana/aws-datasources false false false
lokiStructuredMetadata GA @grafana/observability-logs false false false
76 cachingOptimizeSerializationMemoryUsage experimental @grafana/grafana-operator-experience-squad false false false
77 prometheusCodeModeMetricNamesSearch experimental @grafana/oss-big-tent false false true
78 addFieldFromCalculationStatFunctions GA @grafana/dataviz-squad false false true
228 preferLibraryPanelTitle privatePreview @grafana/dashboards-squad false false false
229 tabularNumbers GA @grafana/grafana-frontend-platform false false false
230 newInfluxDSConfigPageDesign privatePreview @grafana/partner-datasources false false false
231 enableAppChromeExtensions experimental @grafana/plugins-platform-backend false false true
+4 -12
View File
@@ -87,10 +87,6 @@ const (
// Split large interval queries into subqueries with smaller time intervals
FlagLokiQuerySplitting = "lokiQuerySplitting"
// FlagLokiQuerySplittingConfig
// Give users the option to configure split durations for Loki queries
FlagLokiQuerySplittingConfig = "lokiQuerySplittingConfig"
// FlagIndividualCookiePreferences
// Support overriding cookie preferences per user
FlagIndividualCookiePreferences = "individualCookiePreferences"
@@ -143,10 +139,6 @@ const (
// Enables extra themes
FlagExtraThemes = "extraThemes"
// FlagLokiPredefinedOperations
// Adds predefined query operations to Loki query editor
FlagLokiPredefinedOperations = "lokiPredefinedOperations"
// FlagPluginsFrontendSandbox
// Enables the plugins frontend sandbox
FlagPluginsFrontendSandbox = "pluginsFrontendSandbox"
@@ -311,10 +303,6 @@ const (
// Runs CloudWatch metrics queries as separate batches
FlagCloudWatchBatchQueries = "cloudWatchBatchQueries"
// FlagLokiStructuredMetadata
// Enables the loki data source to request structured metadata from the Loki server
FlagLokiStructuredMetadata = "lokiStructuredMetadata"
// FlagCachingOptimizeSerializationMemoryUsage
// If enabled, the caching backend gradually serializes query responses for the cache, comparing against the configured `[caching]max_value_mb` value as it goes. This can can help prevent Grafana from running out of memory while attempting to cache very large query responses.
FlagCachingOptimizeSerializationMemoryUsage = "cachingOptimizeSerializationMemoryUsage"
@@ -934,4 +922,8 @@ const (
// FlagNewInfluxDSConfigPageDesign
// Enables new design for the InfluxDB data source configuration page
FlagNewInfluxDSConfigPageDesign = "newInfluxDSConfigPageDesign"
// FlagEnableAppChromeExtensions
// Set this to true to enable all app chrome extensions registered by plugins.
FlagEnableAppChromeExtensions = "enableAppChromeExtensions"
)
+22 -3
View File
@@ -943,6 +943,22 @@
"codeowner": "@grafana/aws-datasources"
}
},
{
"metadata": {
"name": "enableAppChromeExtensions",
"resourceVersion": "1750235111726",
"creationTimestamp": "2025-06-18T08:25:11Z"
},
"spec": {
"description": "Set this to true to enable all app chrome extensions registered by plugins.",
"stage": "experimental",
"codeowner": "@grafana/plugins-platform-backend",
"frontend": true,
"hideFromAdminPage": true,
"hideFromDocs": true,
"expression": "false"
}
},
{
"metadata": {
"name": "enableDatagridEditing",
@@ -1830,7 +1846,8 @@
"metadata": {
"name": "lokiPredefinedOperations",
"resourceVersion": "1750434297879",
"creationTimestamp": "2023-06-02T10:52:36Z"
"creationTimestamp": "2023-06-02T10:52:36Z",
"deletionTimestamp": "2025-06-27T08:08:44Z"
},
"spec": {
"description": "Adds predefined query operations to Loki query editor",
@@ -1872,7 +1889,8 @@
"metadata": {
"name": "lokiQuerySplittingConfig",
"resourceVersion": "1750434297879",
"creationTimestamp": "2023-03-20T15:51:36Z"
"creationTimestamp": "2023-03-20T15:51:36Z",
"deletionTimestamp": "2025-06-27T09:32:43Z"
},
"spec": {
"description": "Give users the option to configure split durations for Loki queries",
@@ -1910,7 +1928,8 @@
"metadata": {
"name": "lokiStructuredMetadata",
"resourceVersion": "1750434297879",
"creationTimestamp": "2023-11-16T16:06:14Z"
"creationTimestamp": "2023-11-16T16:06:14Z",
"deletionTimestamp": "2025-06-27T09:00:53Z"
},
"spec": {
"description": "Enables the loki data source to request structured metadata from the Loki server",
+10 -10
View File
@@ -358,7 +358,7 @@ func TestIntegrationFolderService(t *testing.T) {
})
}
func TestIntegrationNestedFolderService(t *testing.T) {
func TestIntegrationNestedFolderServiceBasicOperations(t *testing.T) {
if testing.Short() {
t.Skip("skipping integration test")
}
@@ -438,7 +438,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOn, db, serviceWithFlagOn, dashSrv, ac, b)
require.NoError(t, err)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOn, featuresFlagOn, ac, dashSrv)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOn, featuresFlagOn, ac, dashSrv, nil, nil)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOn)
require.NoError(t, err)
@@ -517,7 +517,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
alertStore, err := ngstore.ProvideDBStore(cfg, featuresFlagOff, db, serviceWithFlagOff, dashSrv, ac, b)
require.NoError(t, err)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOff, featuresFlagOff, ac, dashSrv)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, serviceWithFlagOff, featuresFlagOff, ac, dashSrv, nil, nil)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, serviceWithFlagOff)
require.NoError(t, err)
@@ -655,7 +655,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
require.NoError(t, err)
dashSrv.RegisterDashboardPermissions(dashboardPermissions)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, tc.service, tc.featuresFlag, ac, dashSrv)
elementService := libraryelements.ProvideService(cfg, db, routeRegister, tc.service, tc.featuresFlag, ac, dashSrv, nil, nil)
lps, err := librarypanels.ProvideService(cfg, db, routeRegister, elementService, tc.service)
require.NoError(t, err)
@@ -726,7 +726,7 @@ func TestIntegrationNestedFolderService(t *testing.T) {
})
}
func TestNestedFolderServiceFeatureToggle(t *testing.T) {
func TestIntegrationNestedFolderServiceFeatureToggle(t *testing.T) {
nestedFolderStore := folder.NewFakeStore()
dashStore := dashboards.FakeDashboardStore{}
@@ -757,7 +757,7 @@ func TestNestedFolderServiceFeatureToggle(t *testing.T) {
})
}
func TestFolderServiceDualWrite(t *testing.T) {
func TestIntegrationFolderServiceDualWrite(t *testing.T) {
db, _ := sqlstore.InitTestDB(t)
cfg := setting.NewCfg()
features := featuremgmt.WithFeatures()
@@ -816,7 +816,7 @@ func TestFolderServiceDualWrite(t *testing.T) {
})
}
func TestNestedFolderService(t *testing.T) {
func TestIntegrationNestedFolderService(t *testing.T) {
t.Run("with feature flag unset", func(t *testing.T) {
t.Run("Should create a folder in both dashboard and folders tables", func(t *testing.T) {
// dash is needed here because folderSvc.Create expects SaveDashboard to return it
@@ -1690,7 +1690,7 @@ func TestIntegrationNestedFolderSharedWithMe(t *testing.T) {
})
}
func TestFolderServiceGetFolder(t *testing.T) {
func TestIntegrationFolderServiceGetFolder(t *testing.T) {
db, _ := sqlstore.InitTestDB(t)
signedInAdminUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
@@ -1801,7 +1801,7 @@ func TestFolderServiceGetFolder(t *testing.T) {
}
}
func TestFolderServiceGetFolders(t *testing.T) {
func TestIntegrationFolderServiceGetFolders(t *testing.T) {
db, cfg := sqlstore.InitTestDB(t)
folderStore := ProvideDashboardFolderStore(db)
@@ -1872,7 +1872,7 @@ func TestFolderServiceGetFolders(t *testing.T) {
// TODO replace it with an API test under /pkg/tests/api/folders
// whenever the golang client with get updated to allow filtering child folders by permission
func TestGetChildrenFilterByPermission(t *testing.T) {
func TestIntegrationGetChildrenFilterByPermission(t *testing.T) {
db, cfg := sqlstore.InitTestDB(t)
signedInAdminUser := user.SignedInUser{UserID: 1, OrgID: orgID, Permissions: map[int64]map[string][]string{
@@ -847,7 +847,7 @@ func TestGetFoldersFromApiServer(t *testing.T) {
})
}
func TestDeleteFoldersFromApiServer(t *testing.T) {
func TestIntegrationDeleteFoldersFromApiServer(t *testing.T) {
fakeK8sClient := new(client.MockK8sHandler)
fakeK8sClient.On("GetNamespace", mock.Anything, mock.Anything).Return("default")
dashboardK8sclient := new(client.MockK8sHandler)

Some files were not shown because too many files have changed in this diff Show More