Merge remote-tracking branch 'origin/main' into ds-apiserver-with-configs
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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
@@ -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")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
-2
@@ -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
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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: "../../../",
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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{
|
||||
|
||||
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
-1
@@ -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
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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"))
|
||||
|
||||
@@ -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
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
@@ -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"
|
||||
)
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user