b80fbe03f0
* add core plugin flow * add instrumentation * move func * remove cruft * support external backend plugins * refactor + clean up * remove comments * refactor loader * simplify core plugin path arg * cleanup loggers * move signature validator to plugins package * fix sig packaging * cleanup plugin model * remove unnecessary plugin field * add start+stop for pm * fix failures * add decommissioned state * export fields just to get things flowing * fix comments * set static routes * make image loading idempotent * merge with backend plugin manager * re-use funcs * reorder imports + remove unnecessary interface * add some TODOs + remove unused func * remove unused instrumentation func * simplify client usage * remove import alias * re-use backendplugin.Plugin interface * re order funcs * improve var name * fix log statements * refactor data model * add logic for dupe check during loading * cleanup state setting * refactor loader * cleanup manager interface * add rendering flow * refactor loading + init * add renderer support * fix renderer plugin * reformat imports * track errors * fix plugin signature inheritance * name param in interface * update func comment * fix func arg name * introduce class concept * remove func * fix external plugin check * apply changes from pm-experiment * fix core plugins * fix imports * rename interface * comment API interface * add support for testdata plugin * enable alerting + use correct core plugin contracts * slim manager API * fix param name * fix filter * support static routes * fix rendering * tidy rendering * get tests compiling * fix install+uninstall * start finder test * add finder test coverage * start loader tests * add test for core plugins * load core + bundled test * add test for nested plugin loading * add test files * clean interface + fix registering some core plugins * refactoring * reformat and create sub packages * simplify core plugin init * fix ctx cancel scenario * migrate initializer * remove Init() funcs * add test starter * new logger * flesh out initializer tests * refactoring * remove unused svc * refactor rendering flow * fixup loader tests * add enabled helper func * fix logger name * fix data fetchers * fix case where plugin dir doesn't exist * improve coverage + move dupe checking to loader * remove noisy debug logs * register core plugins automagically * add support for renderer in catalog * make private func + fix req validation * use interface * re-add check for renderer in catalog * tidy up from moving to auto reg core plugins * core plugin registrar * guards * copy over core plugins for test infra * all tests green * renames * propagate new interfaces * kill old manager * get compiling * tidy up * update naming * refactor manager test + cleanup * add more cases to finder test * migrate validator to field * more coverage * refactor dupe checking * add test for plugin class * add coverage for initializer * split out rendering * move * fixup tests * fix uss test * fix frontend settings * fix grafanads test * add check when checking sig errors * fix enabled map * fixup * allow manual setup of CM * rename to cloud-monitoring * remove TODO * add installer interface for testing * loader interface returns * tests passing * refactor + add more coverage * support 'stackdriver' * fix frontend settings loading * improve naming based on package name * small tidy * refactor test * fix renderer start * make cloud-monitoring plugin ID clearer * add plugin update test * add integration tests * don't break all if sig can't be calculated * add root URL check test * add more signature verification tests * update DTO name * update enabled plugins comment * update comments * fix linter * revert fe naming change * fix errors endpoint * reset error code field name * re-order test to help verify * assert -> require * pm check * add missing entry + re-order * re-check * dump icon log * verify manager contents first * reformat * apply PR feedback * apply style changes * fix one vs all loading err * improve log output * only start when no signature error * move log * rework plugin update check * fix test * fix multi loading from cfg.PluginSettings * improve log output #2 * add error abstraction to capture errors without registering a plugin * add debug log * add unsigned warning * e2e test attempt * fix logger * set home path * prevent panic * alternate * ugh.. fix home path * return renderer even if not started * make renderer plugin managed * add fallback renderer icon, update renderer badge + prevent changes when renderer is installed * fix icon loading * rollback renderer changes * use correct field * remove unneccessary block * remove newline * remove unused func * fix bundled plugins base + module fields * remove unused field since refactor * add authorizer abstraction * loader only returns plugins expected to run * fix multi log output
307 lines
9.6 KiB
Go
307 lines
9.6 KiB
Go
package testinfra
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net"
|
|
"net/http"
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/grafana/grafana/pkg/api"
|
|
"github.com/grafana/grafana/pkg/infra/fs"
|
|
"github.com/grafana/grafana/pkg/models"
|
|
"github.com/grafana/grafana/pkg/server"
|
|
"github.com/grafana/grafana/pkg/services/sqlstore"
|
|
"github.com/grafana/grafana/pkg/setting"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"gopkg.in/ini.v1"
|
|
)
|
|
|
|
// StartGrafana starts a Grafana server.
|
|
// The server address is returned.
|
|
func StartGrafana(t *testing.T, grafDir, cfgPath string) (string, *sqlstore.SQLStore) {
|
|
t.Helper()
|
|
ctx := context.Background()
|
|
|
|
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.SQLStore
|
|
}
|
|
|
|
// SetUpDatabase sets up the Grafana database.
|
|
func SetUpDatabase(t *testing.T, grafDir string) *sqlstore.SQLStore {
|
|
t.Helper()
|
|
|
|
sqlStore := sqlstore.InitTestDB(t, sqlstore.InitTestDBOpt{
|
|
EnsureDefaultOrgAndUser: true,
|
|
})
|
|
// We need the main org, since it's used for anonymous access
|
|
org, err := sqlStore.GetOrgByName(sqlstore.MainOrgName)
|
|
require.NoError(t, err)
|
|
require.NotNil(t, org)
|
|
|
|
// Make sure changes are synced with other goroutines
|
|
err = sqlStore.Sync()
|
|
require.NoError(t, err)
|
|
|
|
return sqlStore
|
|
}
|
|
|
|
// CreateGrafDir creates the Grafana directory.
|
|
func CreateGrafDir(t *testing.T, opts ...GrafanaOpts) (string, string) {
|
|
t.Helper()
|
|
|
|
tmpDir, err := ioutil.TempDir("", "")
|
|
require.NoError(t, err)
|
|
t.Cleanup(func() {
|
|
err := os.RemoveAll(tmpDir)
|
|
assert.NoError(t, err)
|
|
})
|
|
|
|
// 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)
|
|
|
|
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)
|
|
|
|
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.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)
|
|
}
|
|
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.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)
|
|
}
|
|
}
|
|
|
|
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 models.RoleType
|
|
EnableQuota bool
|
|
DisableAnonymous bool
|
|
CatalogAppEnabled bool
|
|
ViewersCanEdit bool
|
|
PluginAdminEnabled bool
|
|
DisableLegacyAlerting bool
|
|
EnableUnifiedAlerting bool
|
|
UnifiedAlertingDisabledOrgs []int64
|
|
}
|