grafana/pkg/tests/testinfra/testinfra.go
Marcus Efraimsson 6dbe3b555f
Plugins: Refactor forward of cookies, OAuth token and header modifications by introducing client middlewares (#58132)
Adding support for backend plugin client middlewares. This allows headers in outgoing 
backend plugin and HTTP requests to be modified using client middlewares.

The following client middlewares added:
Forward cookies: Will forward incoming HTTP request Cookies to outgoing plugins.Client 
and HTTP requests if the datasource has enabled forwarding of cookies (keepCookies).
Forward OAuth token: Will set OAuth token headers on outgoing plugins.Client and HTTP 
requests if the datasource has enabled Forward OAuth Identity (oauthPassThru).
Clear auth headers: Will clear any outgoing HTTP headers that was part of the incoming 
HTTP request and used when authenticating to Grafana.
The current suggested way to register client middlewares is to have a separate package, 
pluginsintegration, responsible for bootstrap/instantiate the backend plugin client with 
middlewares and/or longer term bootstrap/instantiate plugin management. 

Fixes #54135
Related to #47734
Related to #57870
Related to #41623
Related to #57065
2022-12-01 19:08:36 +01:00

377 lines
12 KiB
Go

package testinfra
import (
"context"
"fmt"
"net"
"net/http"
"os"
"path/filepath"
"strconv"
"strings"
"testing"
"time"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"gopkg.in/ini.v1"
"github.com/grafana/grafana/pkg/api"
"github.com/grafana/grafana/pkg/extensions"
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/infra/fs"
"github.com/grafana/grafana/pkg/server"
"github.com/grafana/grafana/pkg/services/org"
"github.com/grafana/grafana/pkg/services/sqlstore"
"github.com/grafana/grafana/pkg/services/user"
"github.com/grafana/grafana/pkg/setting"
)
// StartGrafana starts a Grafana server.
// The server address is returned.
func StartGrafana(t *testing.T, grafDir, cfgPath string) (string, *sqlstore.SQLStore) {
addr, env := StartGrafanaEnv(t, grafDir, cfgPath)
return addr, env.SQLStore
}
func StartGrafanaEnv(t *testing.T, grafDir, cfgPath string) (string, *server.TestEnv) {
t.Helper()
ctx := context.Background()
setting.IsEnterprise = extensions.IsEnterprise
listener, err := net.Listen("tcp", "127.0.0.1:0")
require.NoError(t, err)
cmdLineArgs := setting.CommandLineArgs{Config: cfgPath, HomePath: grafDir}
serverOpts := server.Options{Listener: listener, HomePath: grafDir}
apiServerOpts := api.ServerOptions{Listener: listener}
env, err := server.InitializeForTest(cmdLineArgs, serverOpts, apiServerOpts)
require.NoError(t, err)
require.NoError(t, env.SQLStore.Sync())
go func() {
// When the server runs, it will also build and initialize the service graph
if err := env.Server.Run(); err != nil {
t.Log("Server exited uncleanly", "error", err)
}
}()
t.Cleanup(func() {
if err := env.Server.Shutdown(ctx, "test cleanup"); err != nil {
t.Error("Timed out waiting on server to shut down")
}
})
// Wait for Grafana to be ready
addr := listener.Addr().String()
resp, err := http.Get(fmt.Sprintf("http://%s/api/health", addr))
require.NoError(t, err)
require.NotNil(t, resp)
t.Cleanup(func() {
err := resp.Body.Close()
assert.NoError(t, err)
})
require.Equal(t, 200, resp.StatusCode)
t.Logf("Grafana is listening on %s", addr)
return addr, env
}
// SetUpDatabase sets up the Grafana database.
func SetUpDatabase(t *testing.T, grafDir string) *sqlstore.SQLStore {
t.Helper()
sqlStore := db.InitTestDB(t, sqlstore.InitTestDBOpt{
EnsureDefaultOrgAndUser: true,
})
// Make sure changes are synced with other goroutines
err := sqlStore.Sync()
require.NoError(t, err)
return sqlStore
}
// CreateGrafDir creates the Grafana directory.
// The log by default is muted in the regression test, to activate it, pass option EnableLog = true
func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
t.Helper()
tmpDir := t.TempDir()
// Search upwards in directory tree for project root
var rootDir string
found := false
for i := 0; i < 20; i++ {
rootDir = filepath.Join(rootDir, "..")
dir, err := filepath.Abs(rootDir)
require.NoError(t, err)
exists, err := fs.Exists(filepath.Join(dir, "public", "views"))
require.NoError(t, err)
if exists {
rootDir = dir
found = true
break
}
}
require.True(t, found, "Couldn't detect project root directory")
cfgDir := filepath.Join(tmpDir, "conf")
err := os.MkdirAll(cfgDir, 0750)
require.NoError(t, err)
dataDir := filepath.Join(tmpDir, "data")
// nolint:gosec
err = os.MkdirAll(dataDir, 0750)
require.NoError(t, err)
logsDir := filepath.Join(tmpDir, "logs")
pluginsDir := filepath.Join(tmpDir, "plugins")
publicDir := filepath.Join(tmpDir, "public")
err = os.MkdirAll(publicDir, 0750)
require.NoError(t, err)
viewsDir := filepath.Join(publicDir, "views")
err = fs.CopyRecursive(filepath.Join(rootDir, "public", "views"), viewsDir)
require.NoError(t, err)
// Copy index template to index.html, since Grafana will try to use the latter
err = fs.CopyFile(filepath.Join(rootDir, "public", "views", "index-template.html"),
filepath.Join(viewsDir, "index.html"))
require.NoError(t, err)
// Copy error template to error.html, since Grafana will try to use the latter
err = fs.CopyFile(filepath.Join(rootDir, "public", "views", "error-template.html"),
filepath.Join(viewsDir, "error.html"))
require.NoError(t, err)
emailsDir := filepath.Join(publicDir, "emails")
err = fs.CopyRecursive(filepath.Join(rootDir, "public", "emails"), emailsDir)
require.NoError(t, err)
provDir := filepath.Join(cfgDir, "provisioning")
provDSDir := filepath.Join(provDir, "datasources")
err = os.MkdirAll(provDSDir, 0750)
require.NoError(t, err)
provNotifiersDir := filepath.Join(provDir, "notifiers")
err = os.MkdirAll(provNotifiersDir, 0750)
require.NoError(t, err)
provPluginsDir := filepath.Join(provDir, "plugins")
err = os.MkdirAll(provPluginsDir, 0750)
require.NoError(t, err)
provDashboardsDir := filepath.Join(provDir, "dashboards")
err = os.MkdirAll(provDashboardsDir, 0750)
require.NoError(t, err)
corePluginsDir := filepath.Join(publicDir, "app/plugins")
err = fs.CopyRecursive(filepath.Join(rootDir, "public", "app/plugins"), corePluginsDir)
require.NoError(t, err)
cfg := ini.Empty()
dfltSect := cfg.Section("")
_, err = dfltSect.NewKey("app_mode", "development")
require.NoError(t, err)
pathsSect, err := cfg.NewSection("paths")
require.NoError(t, err)
_, err = pathsSect.NewKey("data", dataDir)
require.NoError(t, err)
_, err = pathsSect.NewKey("logs", logsDir)
require.NoError(t, err)
_, err = pathsSect.NewKey("plugins", pluginsDir)
require.NoError(t, err)
logSect, err := cfg.NewSection("log")
require.NoError(t, err)
_, err = logSect.NewKey("level", "debug")
require.NoError(t, err)
serverSect, err := cfg.NewSection("server")
require.NoError(t, err)
_, err = serverSect.NewKey("port", "0")
require.NoError(t, err)
_, err = serverSect.NewKey("static_root_path", publicDir)
require.NoError(t, err)
anonSect, err := cfg.NewSection("auth.anonymous")
require.NoError(t, err)
_, err = anonSect.NewKey("enabled", "true")
require.NoError(t, err)
alertingSect, err := cfg.NewSection("alerting")
require.NoError(t, err)
_, err = alertingSect.NewKey("notification_timeout_seconds", "1")
require.NoError(t, err)
_, err = alertingSect.NewKey("max_attempts", "3")
require.NoError(t, err)
rbacSect, err := cfg.NewSection("rbac")
require.NoError(t, err)
_, err = rbacSect.NewKey("permission_cache", "false")
require.NoError(t, err)
getOrCreateSection := func(name string) (*ini.Section, error) {
section, err := cfg.GetSection(name)
if err != nil {
return cfg.NewSection(name)
}
return section, err
}
for _, o := range opts {
if o.EnableCSP {
securitySect, err := cfg.NewSection("security")
require.NoError(t, err)
_, err = securitySect.NewKey("content_security_policy", "true")
require.NoError(t, err)
}
if len(o.EnableFeatureToggles) > 0 {
featureSection, err := cfg.NewSection("feature_toggles")
require.NoError(t, err)
_, err = featureSection.NewKey("enable", strings.Join(o.EnableFeatureToggles, " "))
require.NoError(t, err)
}
if o.NGAlertAdminConfigPollInterval != 0 {
ngalertingSection, err := cfg.NewSection("unified_alerting")
require.NoError(t, err)
_, err = ngalertingSection.NewKey("admin_config_poll_interval", o.NGAlertAdminConfigPollInterval.String())
require.NoError(t, err)
}
if o.NGAlertAlertmanagerConfigPollInterval != 0 {
ngalertingSection, err := getOrCreateSection("unified_alerting")
require.NoError(t, err)
_, err = ngalertingSection.NewKey("alertmanager_config_poll_interval", o.NGAlertAlertmanagerConfigPollInterval.String())
require.NoError(t, err)
}
if o.AppModeProduction {
_, err = dfltSect.NewKey("app_mode", "production")
require.NoError(t, err)
}
if o.AnonymousUserRole != "" {
_, err = anonSect.NewKey("org_role", string(o.AnonymousUserRole))
require.NoError(t, err)
}
if o.EnableQuota {
quotaSection, err := cfg.NewSection("quota")
require.NoError(t, err)
_, err = quotaSection.NewKey("enabled", "true")
require.NoError(t, err)
dashboardQuota := int64(100)
if o.DashboardOrgQuota != nil {
dashboardQuota = *o.DashboardOrgQuota
}
_, err = quotaSection.NewKey("org_dashboard", strconv.FormatInt(dashboardQuota, 10))
require.NoError(t, err)
}
if o.DisableAnonymous {
anonSect, err := cfg.GetSection("auth.anonymous")
require.NoError(t, err)
_, err = anonSect.NewKey("enabled", "false")
require.NoError(t, err)
}
if o.PluginAdminEnabled {
anonSect, err := cfg.NewSection("plugins")
require.NoError(t, err)
_, err = anonSect.NewKey("plugin_admin_enabled", "true")
require.NoError(t, err)
}
if o.PluginAdminExternalManageEnabled {
anonSect, err := cfg.NewSection("plugins")
require.NoError(t, err)
_, err = anonSect.NewKey("plugin_admin_external_manage_enabled", "true")
require.NoError(t, err)
}
if o.ViewersCanEdit {
usersSection, err := cfg.NewSection("users")
require.NoError(t, err)
_, err = usersSection.NewKey("viewers_can_edit", "true")
require.NoError(t, err)
}
if o.DisableLegacyAlerting {
alertingSection, err := cfg.GetSection("alerting")
require.NoError(t, err)
_, err = alertingSection.NewKey("enabled", "false")
require.NoError(t, err)
}
if o.EnableUnifiedAlerting {
unifiedAlertingSection, err := getOrCreateSection("unified_alerting")
require.NoError(t, err)
_, err = unifiedAlertingSection.NewKey("enabled", "true")
require.NoError(t, err)
}
if len(o.UnifiedAlertingDisabledOrgs) > 0 {
unifiedAlertingSection, err := getOrCreateSection("unified_alerting")
require.NoError(t, err)
disableOrgStr := strings.Join(strings.Split(strings.Trim(fmt.Sprint(o.UnifiedAlertingDisabledOrgs), "[]"), " "), ",")
_, err = unifiedAlertingSection.NewKey("disabled_orgs", disableOrgStr)
require.NoError(t, err)
}
if !o.EnableLog {
logSection, err := getOrCreateSection("log")
require.NoError(t, err)
_, err = logSection.NewKey("enabled", "false")
require.NoError(t, err)
} else {
serverSection, err := getOrCreateSection("server")
require.NoError(t, err)
_, err = serverSection.NewKey("router_logging", "true")
require.NoError(t, err)
}
if o.GRPCServerAddress != "" {
logSection, err := getOrCreateSection("grpc_server")
require.NoError(t, err)
_, err = logSection.NewKey("address", o.GRPCServerAddress)
require.NoError(t, err)
}
// retry queries 3 times by default
queryRetries := 3
if o.QueryRetries != 0 {
queryRetries = int(o.QueryRetries)
}
logSection, err := getOrCreateSection("database")
require.NoError(t, err)
_, err = logSection.NewKey("query_retries", fmt.Sprintf("%d", queryRetries))
require.NoError(t, err)
}
cfgPath := filepath.Join(cfgDir, "test.ini")
err = cfg.SaveTo(cfgPath)
require.NoError(t, err)
err = fs.CopyFile(filepath.Join(rootDir, "conf", "defaults.ini"), filepath.Join(cfgDir, "defaults.ini"))
require.NoError(t, err)
return tmpDir, cfgPath
}
type GrafanaOpts struct {
EnableCSP bool
EnableFeatureToggles []string
NGAlertAdminConfigPollInterval time.Duration
NGAlertAlertmanagerConfigPollInterval time.Duration
AnonymousUserRole org.RoleType
EnableQuota bool
DashboardOrgQuota *int64
DisableAnonymous bool
CatalogAppEnabled bool
ViewersCanEdit bool
PluginAdminEnabled bool
PluginAdminExternalManageEnabled bool
AppModeProduction bool
DisableLegacyAlerting bool
EnableUnifiedAlerting bool
UnifiedAlertingDisabledOrgs []int64
EnableLog bool
GRPCServerAddress string
QueryRetries int64
}
func CreateUser(t *testing.T, store *sqlstore.SQLStore, cmd user.CreateUserCommand) int64 {
t.Helper()
store.Cfg.AutoAssignOrg = true
store.Cfg.AutoAssignOrgId = 1
u, err := store.CreateUser(context.Background(), cmd)
require.NoError(t, err)
return u.ID
}