Merge branch 'master' into develop

This commit is contained in:
Torkel Ödegaard
2017-12-12 13:19:07 +01:00
63 changed files with 1588 additions and 209 deletions

View File

@@ -25,6 +25,8 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
"gopkg.in/macaron.v1"
gocache "github.com/patrickmn/go-cache"
)
var gravatarSource string
@@ -92,7 +94,7 @@ func (this *Avatar) Update() (err error) {
type CacheServer struct {
notFound *Avatar
cache map[string]*Avatar
cache *gocache.Cache
}
func (this *CacheServer) mustInt(r *http.Request, defaultValue int, keys ...string) (v int) {
@@ -110,7 +112,9 @@ func (this *CacheServer) Handler(ctx *macaron.Context) {
var avatar *Avatar
if avatar, _ = this.cache[hash]; avatar == nil {
if obj, exist := this.cache.Get(hash); exist {
avatar = obj.(*Avatar)
} else {
avatar = New(hash)
}
@@ -124,7 +128,7 @@ func (this *CacheServer) Handler(ctx *macaron.Context) {
if avatar.notFound {
avatar = this.notFound
} else {
this.cache[hash] = avatar
this.cache.Add(hash, avatar, gocache.DefaultExpiration)
}
ctx.Resp.Header().Add("Content-Type", "image/jpeg")
@@ -146,7 +150,7 @@ func NewCacheServer() *CacheServer {
return &CacheServer{
notFound: newNotFound(),
cache: make(map[string]*Avatar),
cache: gocache.New(time.Hour, time.Hour*2),
}
}

View File

@@ -6,6 +6,8 @@ import (
"os"
"path"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/api/dtos"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/dashdiffs"
@@ -15,7 +17,6 @@ import (
"github.com/grafana/grafana/pkg/middleware"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/guardian"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
@@ -157,13 +158,6 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
dash := cmd.GetDashboardModel()
// look up existing dashboard
if dash.Id > 0 {
if existing, _ := getDashboardHelper(c.OrgId, "", dash.Id); existing != nil {
dash.HasAcl = existing.HasAcl
}
}
guardian := guardian.NewDashboardGuardian(dash.Id, c.OrgId, c.SignedInUser)
if canSave, err := guardian.CanSave(); err != nil || !canSave {
return dashboardGuardianResponse(err)
@@ -188,17 +182,23 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
}
}
validateAlertsCmd := alerting.ValidateDashboardAlertsCommand{
dashItem := &dashboards.SaveDashboardItem{
Dashboard: dash,
Message: cmd.Message,
OrgId: c.OrgId,
UserId: c.UserId,
Dashboard: dash,
}
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
dashboard, err := dashboards.GetRepository().SaveDashboard(dashItem)
if err == m.ErrDashboardTitleEmpty {
return ApiError(400, m.ErrDashboardTitleEmpty.Error(), nil)
}
if err == m.ErrDashboardContainsInvalidAlertData {
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
}
err := bus.Dispatch(&cmd)
if err != nil {
if err == m.ErrDashboardWithSameNameExists {
return Json(412, util.DynMap{"status": "name-exists", "message": err.Error()})
@@ -220,18 +220,16 @@ func PostDashboard(c *middleware.Context, cmd m.SaveDashboardCommand) Response {
return ApiError(500, "Failed to save dashboard", err)
}
alertCmd := alerting.UpdateDashboardAlertsCommand{
OrgId: c.OrgId,
UserId: c.UserId,
Dashboard: cmd.Result,
}
if err := bus.Dispatch(&alertCmd); err != nil {
return ApiError(500, "Failed to save alerts", err)
if err == m.ErrDashboardFailedToUpdateAlertData {
return ApiError(500, "Invalid alert data. Cannot save dashboard", err)
}
c.TimeRequest(metrics.M_Api_Dashboard_Save)
return Json(200, util.DynMap{"status": "success", "slug": cmd.Result.Slug, "version": cmd.Result.Version, "id": cmd.Result.Id})
return Json(200, util.DynMap{"status": "success", "slug": dashboard.Slug, "version": dashboard.Version, "id": dashboard.Id})
}
func canEditDashboard(role m.RoleType) bool {
return role == m.ROLE_ADMIN || role == m.ROLE_EDITOR || role == m.ROLE_READ_ONLY_EDITOR
}
func GetHomeDashboard(c *middleware.Context) Response {

View File

@@ -95,7 +95,7 @@ func (hs *HttpServer) Start(ctx context.Context) error {
func (hs *HttpServer) Shutdown(ctx context.Context) error {
err := hs.httpSrv.Shutdown(ctx)
hs.log.Info("stopped http server")
hs.log.Info("Stopped HTTP server")
return err
}

View File

@@ -26,6 +26,7 @@ func RenderToPng(c *middleware.Context) {
UserId: c.UserId,
OrgRole: c.OrgRole,
Timezone: queryReader.Get("tz", ""),
Encoding: queryReader.Get("encoding", ""),
}
pngPath, err := renderer.RenderToPng(renderOpts)

View File

@@ -14,8 +14,8 @@ import (
"net/http"
_ "net/http/pprof"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
_ "github.com/grafana/grafana/pkg/services/alerting/conditions"
@@ -40,9 +40,6 @@ var homePath = flag.String("homepath", "", "path to grafana install/home path, d
var pidFile = flag.String("pidfile", "", "path to pid file")
var exitChan = make(chan int)
func init() {
}
func main() {
v := flag.Bool("v", false, "prints current version and exits")
profile := flag.Bool("profile", false, "Turn on pprof profiling")
@@ -82,12 +79,28 @@ func main() {
setting.BuildStamp = buildstampInt64
metrics.M_Grafana_Version.WithLabelValues(version).Set(1)
shutdownCompleted := make(chan int)
server := NewGrafanaServer()
server.Start()
go listenToSystemSignals(server, shutdownCompleted)
go func() {
code := 0
if err := server.Start(); err != nil {
log.Error2("Startup failed", "error", err)
code = 1
}
exitChan <- code
}()
code := <-shutdownCompleted
log.Info2("Grafana shutdown completed.", "code", code)
log.Close()
os.Exit(code)
}
func listenToSystemSignals(server models.GrafanaServer) {
func listenToSystemSignals(server *GrafanaServerImpl, shutdownCompleted chan int) {
signalChan := make(chan os.Signal, 1)
ignoreChan := make(chan os.Signal, 1)
code := 0
@@ -97,10 +110,12 @@ func listenToSystemSignals(server models.GrafanaServer) {
select {
case sig := <-signalChan:
// Stops trace if profiling has been enabled
trace.Stop()
trace.Stop() // Stops trace if profiling has been enabled
server.Shutdown(0, fmt.Sprintf("system signal: %s", sig))
shutdownCompleted <- 0
case code = <-exitChan:
trace.Stop() // Stops trace if profiling has been enabled
server.Shutdown(code, "startup error")
shutdownCompleted <- code
}
}

View File

@@ -11,7 +11,6 @@ import (
"strconv"
"time"
"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
"github.com/grafana/grafana/pkg/services/provisioning"
"golang.org/x/sync/errgroup"
@@ -20,7 +19,6 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/login"
"github.com/grafana/grafana/pkg/metrics"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/plugins"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/cleanup"
@@ -33,7 +31,7 @@ import (
"github.com/grafana/grafana/pkg/tracing"
)
func NewGrafanaServer() models.GrafanaServer {
func NewGrafanaServer() *GrafanaServerImpl {
rootCtx, shutdownFn := context.WithCancel(context.Background())
childRoutines, childCtx := errgroup.WithContext(rootCtx)
@@ -54,9 +52,7 @@ type GrafanaServerImpl struct {
httpServer *api.HttpServer
}
func (g *GrafanaServerImpl) Start() {
go listenToSystemSignals(g)
func (g *GrafanaServerImpl) Start() error {
g.initLogging()
g.writePIDFile()
@@ -68,17 +64,13 @@ func (g *GrafanaServerImpl) Start() {
social.NewOAuthService()
plugins.Init()
if err := provisioning.StartUp(setting.DatasourcesPath); err != nil {
logger.Error("Failed to provision Grafana from config", "error", err)
g.Shutdown(1, "Startup failed")
return
if err := provisioning.Init(g.context, setting.HomePath, setting.Cfg); err != nil {
return fmt.Errorf("Failed to provision Grafana from config. error: %v", err)
}
closer, err := tracing.Init(setting.Cfg)
if err != nil {
g.log.Error("Tracing settings is not valid", "error", err)
g.Shutdown(1, "Startup failed")
return
return fmt.Errorf("Tracing settings is not valid. error: %v", err)
}
defer closer.Close()
@@ -93,13 +85,12 @@ func (g *GrafanaServerImpl) Start() {
g.childRoutines.Go(func() error { return cleanUpService.Run(g.context) })
if err = notifications.Init(); err != nil {
g.log.Error("Notification service failed to initialize", "error", err)
g.Shutdown(1, "Startup failed")
return
return fmt.Errorf("Notification service failed to initialize. error: %v", err)
}
SendSystemdNotification("READY=1")
g.startHttpServer()
sendSystemdNotification("READY=1")
return g.startHttpServer()
}
func initSql() {
@@ -123,16 +114,16 @@ func (g *GrafanaServerImpl) initLogging() {
setting.LogConfigurationInfo()
}
func (g *GrafanaServerImpl) startHttpServer() {
func (g *GrafanaServerImpl) startHttpServer() error {
g.httpServer = api.NewHttpServer()
err := g.httpServer.Start(g.context)
if err != nil {
g.log.Error("Fail to start server", "error", err)
g.Shutdown(1, "Startup failed")
return
return fmt.Errorf("Fail to start server. error: %v", err)
}
return nil
}
func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
@@ -145,10 +136,9 @@ func (g *GrafanaServerImpl) Shutdown(code int, reason string) {
g.shutdownFn()
err = g.childRoutines.Wait()
g.log.Info("Shutdown completed", "reason", err)
log.Close()
os.Exit(code)
if err != nil && err != context.Canceled {
g.log.Error("Server shutdown completed with an error", "error", err)
}
}
func (g *GrafanaServerImpl) writePIDFile() {
@@ -173,7 +163,7 @@ func (g *GrafanaServerImpl) writePIDFile() {
g.log.Info("Writing PID file", "path", *pidFile, "pid", pid)
}
func SendSystemdNotification(state string) error {
func sendSystemdNotification(state string) error {
notifySocket := os.Getenv("NOTIFY_SOCKET")
if notifySocket == "" {

View File

@@ -31,6 +31,7 @@ type RenderOpts struct {
OrgRole models.RoleType
Timezone string
IsAlertContext bool
Encoding string
}
var ErrTimeout = errors.New("Timeout error. You can set timeout in seconds with &timeout url parameter")
@@ -103,6 +104,10 @@ func RenderToPng(params *RenderOpts) (string, error) {
"renderKey=" + renderKey,
}
if params.Encoding != "" {
cmdArgs = append([]string{fmt.Sprintf("--output-encoding=%s", params.Encoding)}, cmdArgs...)
}
cmd := exec.Command(binPath, cmdArgs...)
stdout, err := cmd.StdoutPipe()

View File

@@ -11,12 +11,14 @@ import (
// Typed errors
var (
ErrDashboardNotFound = errors.New("Dashboard not found")
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
ErrDashboardWithSameNameExists = errors.New("A dashboard with the same name already exists")
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
ErrDashboardNotFound = errors.New("Dashboard not found")
ErrDashboardSnapshotNotFound = errors.New("Dashboard snapshot not found")
ErrDashboardWithSameNameExists = errors.New("A dashboard with the same name already exists")
ErrDashboardVersionMismatch = errors.New("The dashboard has been changed by someone else")
ErrDashboardTitleEmpty = errors.New("Dashboard title cannot be empty")
ErrDashboardFolderCannotHaveParent = errors.New("A Dashboard Folder cannot be added to another folder")
ErrDashboardContainsInvalidAlertData = errors.New("Invalid alert data. Cannot save dashboard")
ErrDashboardFailedToUpdateAlertData = errors.New("Failed to save alert data")
)
type UpdatePluginDashboardError struct {
@@ -156,6 +158,8 @@ type SaveDashboardCommand struct {
FolderId int64 `json:"folderId"`
IsFolder bool `json:"isFolder"`
UpdatedAt time.Time
Result *Dashboard
}

View File

@@ -1,6 +0,0 @@
package models
type GrafanaServer interface {
Start()
Shutdown(code int, reason string)
}

View File

@@ -0,0 +1,82 @@
package dashboards
import (
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
)
type Repository interface {
SaveDashboard(*SaveDashboardItem) (*models.Dashboard, error)
}
var repositoryInstance Repository
func GetRepository() Repository {
return repositoryInstance
}
func SetRepository(rep Repository) {
repositoryInstance = rep
}
type SaveDashboardItem struct {
TitleLower string
OrgId int64
Folder string
UpdatedAt time.Time
UserId int64
Message string
Overwrite bool
Dashboard *models.Dashboard
}
type DashboardRepository struct{}
func (dr *DashboardRepository) SaveDashboard(json *SaveDashboardItem) (*models.Dashboard, error) {
dashboard := json.Dashboard
if dashboard.Title == "" {
return nil, models.ErrDashboardTitleEmpty
}
validateAlertsCmd := alerting.ValidateDashboardAlertsCommand{
OrgId: json.OrgId,
Dashboard: dashboard,
}
if err := bus.Dispatch(&validateAlertsCmd); err != nil {
return nil, models.ErrDashboardContainsInvalidAlertData
}
cmd := models.SaveDashboardCommand{
Dashboard: dashboard.Data,
Message: json.Message,
OrgId: json.OrgId,
Overwrite: json.Overwrite,
UserId: json.UserId,
}
if !json.UpdatedAt.IsZero() {
cmd.UpdatedAt = json.UpdatedAt
}
err := bus.Dispatch(&cmd)
if err != nil {
return nil, err
}
alertCmd := alerting.UpdateDashboardAlertsCommand{
OrgId: json.OrgId,
UserId: json.UserId,
Dashboard: cmd.Result,
}
if err := bus.Dispatch(&alertCmd); err != nil {
return nil, models.ErrDashboardFailedToUpdateAlertData
}
return cmd.Result, nil
}

View File

@@ -0,0 +1,49 @@
package dashboards
import (
"io/ioutil"
"path/filepath"
"strings"
yaml "gopkg.in/yaml.v2"
)
type configReader struct {
path string
}
func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
files, err := ioutil.ReadDir(cr.path)
if err != nil {
return nil, err
}
var dashboards []*DashboardsAsConfig
for _, file := range files {
if !strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml") {
continue
}
filename, _ := filepath.Abs(filepath.Join(cr.path, file.Name()))
yamlFile, err := ioutil.ReadFile(filename)
if err != nil {
return nil, err
}
var datasource []*DashboardsAsConfig
err = yaml.Unmarshal(yamlFile, &datasource)
if err != nil {
return nil, err
}
dashboards = append(dashboards, datasource...)
}
for i := range dashboards {
if dashboards[i].OrgId == 0 {
dashboards[i].OrgId = 1
}
}
return dashboards, nil
}

View File

@@ -0,0 +1,62 @@
package dashboards
import (
"testing"
. "github.com/smartystreets/goconvey/convey"
)
var (
simpleDashboardConfig string = "./test-configs/dashboards-from-disk"
brokenConfigs string = "./test-configs/broken-configs"
)
func TestDashboardsAsConfig(t *testing.T) {
Convey("Dashboards as configuration", t, func() {
Convey("Can read config file", func() {
cfgProvifer := configReader{path: simpleDashboardConfig}
cfg, err := cfgProvifer.readConfig()
if err != nil {
t.Fatalf("readConfig return an error %v", err)
}
So(len(cfg), ShouldEqual, 2)
ds := cfg[0]
So(ds.Name, ShouldEqual, "general dashboards")
So(ds.Type, ShouldEqual, "file")
So(ds.OrgId, ShouldEqual, 2)
So(ds.Folder, ShouldEqual, "developers")
So(ds.Editable, ShouldBeTrue)
So(len(ds.Options), ShouldEqual, 1)
So(ds.Options["folder"], ShouldEqual, "/var/lib/grafana/dashboards")
ds2 := cfg[1]
So(ds2.Name, ShouldEqual, "default")
So(ds2.Type, ShouldEqual, "file")
So(ds2.OrgId, ShouldEqual, 1)
So(ds2.Folder, ShouldEqual, "")
So(ds2.Editable, ShouldBeFalse)
So(len(ds2.Options), ShouldEqual, 1)
So(ds2.Options["folder"], ShouldEqual, "/var/lib/grafana/dashboards")
})
Convey("Should skip broken config files", func() {
cfgProvifer := configReader{path: brokenConfigs}
cfg, err := cfgProvifer.readConfig()
if err != nil {
t.Fatalf("readConfig return an error %v", err)
}
So(len(cfg), ShouldEqual, 0)
})
})
}

View File

@@ -0,0 +1,48 @@
package dashboards
import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/log"
)
type DashboardProvisioner struct {
cfgReader *configReader
log log.Logger
ctx context.Context
}
func Provision(ctx context.Context, configDirectory string) (*DashboardProvisioner, error) {
d := &DashboardProvisioner{
cfgReader: &configReader{path: configDirectory},
log: log.New("provisioning.dashboard"),
ctx: ctx,
}
err := d.Provision(ctx)
return d, err
}
func (provider *DashboardProvisioner) Provision(ctx context.Context) error {
cfgs, err := provider.cfgReader.readConfig()
if err != nil {
return err
}
for _, cfg := range cfgs {
switch cfg.Type {
case "file":
fileReader, err := NewDashboardFileReader(cfg, provider.log.New("type", cfg.Type, "name", cfg.Name))
if err != nil {
return err
}
go fileReader.ReadAndListen(ctx)
default:
return fmt.Errorf("type %s is not supported", cfg.Type)
}
}
return nil
}

View File

@@ -0,0 +1,175 @@
package dashboards
import (
"context"
"fmt"
"os"
"path/filepath"
"strings"
"time"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
gocache "github.com/patrickmn/go-cache"
)
type fileReader struct {
Cfg *DashboardsAsConfig
Path string
log log.Logger
dashboardRepo dashboards.Repository
cache *gocache.Cache
}
func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReader, error) {
path, ok := cfg.Options["folder"].(string)
if !ok {
return nil, fmt.Errorf("Failed to load dashboards. folder param is not a string")
}
if _, err := os.Stat(path); os.IsNotExist(err) {
log.Error("Cannot read directory", "error", err)
}
return &fileReader{
Cfg: cfg,
Path: path,
log: log,
dashboardRepo: dashboards.GetRepository(),
cache: gocache.New(5*time.Minute, 30*time.Minute),
}, nil
}
func (fr *fileReader) addCache(key string, json *dashboards.SaveDashboardItem) {
fr.cache.Add(key, json, time.Minute*10)
}
func (fr *fileReader) getCache(key string) (*dashboards.SaveDashboardItem, bool) {
obj, exist := fr.cache.Get(key)
if !exist {
return nil, exist
}
dash, ok := obj.(*dashboards.SaveDashboardItem)
if !ok {
return nil, ok
}
return dash, ok
}
func (fr *fileReader) ReadAndListen(ctx context.Context) error {
ticker := time.NewTicker(time.Second * 3)
if err := fr.walkFolder(); err != nil {
fr.log.Error("failed to search for dashboards", "error", err)
}
running := false
for {
select {
case <-ticker.C:
if !running { // avoid walking the filesystem in parallel. incase fs is very slow.
running = true
go func() {
fr.walkFolder()
running = false
}()
}
case <-ctx.Done():
return nil
}
}
}
func (fr *fileReader) walkFolder() error {
if _, err := os.Stat(fr.Path); err != nil {
if os.IsNotExist(err) {
return err
}
}
return filepath.Walk(fr.Path, func(path string, fileInfo os.FileInfo, err error) error {
if err != nil {
return err
}
if fileInfo.IsDir() {
if strings.HasPrefix(fileInfo.Name(), ".") {
return filepath.SkipDir
}
return nil
}
if !strings.HasSuffix(fileInfo.Name(), ".json") {
return nil
}
cachedDashboard, exist := fr.getCache(path)
if exist && cachedDashboard.UpdatedAt == fileInfo.ModTime() {
return nil
}
dash, err := fr.readDashboardFromFile(path)
if err != nil {
fr.log.Error("failed to load dashboard from ", "file", path, "error", err)
return nil
}
cmd := &models.GetDashboardQuery{Slug: dash.Dashboard.Slug}
err = bus.Dispatch(cmd)
// if we dont have the dashboard in the db, save it!
if err == models.ErrDashboardNotFound {
fr.log.Debug("saving new dashboard", "file", path)
_, err = fr.dashboardRepo.SaveDashboard(dash)
return err
}
if err != nil {
fr.log.Error("failed to query for dashboard", "slug", dash.Dashboard.Slug, "error", err)
return nil
}
// break if db version is newer then fil version
if cmd.Result.Updated.Unix() >= fileInfo.ModTime().Unix() {
return nil
}
fr.log.Debug("loading dashboard from disk into database.", "file", path)
_, err = fr.dashboardRepo.SaveDashboard(dash)
return err
})
}
func (fr *fileReader) readDashboardFromFile(path string) (*dashboards.SaveDashboardItem, error) {
reader, err := os.Open(path)
if err != nil {
return nil, err
}
defer reader.Close()
data, err := simplejson.NewFromReader(reader)
if err != nil {
return nil, err
}
stat, err := os.Stat(path)
if err != nil {
return nil, err
}
dash, err := createDashboardJson(data, stat.ModTime(), fr.Cfg)
if err != nil {
return nil, err
}
fr.addCache(path, dash)
return dash, nil
}

View File

@@ -0,0 +1,137 @@
package dashboards
import (
"os"
"testing"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/log"
. "github.com/smartystreets/goconvey/convey"
)
var (
defaultDashboards string = "./test-dashboards/folder-one"
brokenDashboards string = "./test-dashboards/broken-dashboards"
oneDashboard string = "./test-dashboards/one-dashboard"
fakeRepo *fakeDashboardRepo
)
func TestDashboardFileReader(t *testing.T) {
Convey("Reading dashboards from disk", t, func() {
bus.ClearBusHandlers()
fakeRepo = &fakeDashboardRepo{}
bus.AddHandler("test", mockGetDashboardQuery)
dashboards.SetRepository(fakeRepo)
logger := log.New("test.logger")
cfg := &DashboardsAsConfig{
Name: "Default",
Type: "file",
OrgId: 1,
Folder: "",
Options: map[string]interface{}{},
}
Convey("Can read default dashboard", func() {
cfg.Options["folder"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, logger)
So(err, ShouldBeNil)
err = reader.walkFolder()
So(err, ShouldBeNil)
So(len(fakeRepo.inserted), ShouldEqual, 2)
})
Convey("Should not update dashboards when db is newer", func() {
cfg.Options["folder"] = oneDashboard
fakeRepo.getDashboard = append(fakeRepo.getDashboard, &models.Dashboard{
Updated: time.Now().Add(time.Hour),
Slug: "grafana",
})
reader, err := NewDashboardFileReader(cfg, logger)
So(err, ShouldBeNil)
err = reader.walkFolder()
So(err, ShouldBeNil)
So(len(fakeRepo.inserted), ShouldEqual, 0)
})
Convey("Can read default dashboard and replace old version in database", func() {
cfg.Options["folder"] = oneDashboard
stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
fakeRepo.getDashboard = append(fakeRepo.getDashboard, &models.Dashboard{
Updated: stat.ModTime().AddDate(0, 0, -1),
Slug: "grafana",
})
reader, err := NewDashboardFileReader(cfg, logger)
So(err, ShouldBeNil)
err = reader.walkFolder()
So(err, ShouldBeNil)
So(len(fakeRepo.inserted), ShouldEqual, 1)
})
Convey("Invalid configuration should return error", func() {
cfg := &DashboardsAsConfig{
Name: "Default",
Type: "file",
OrgId: 1,
Folder: "",
}
_, err := NewDashboardFileReader(cfg, logger)
So(err, ShouldNotBeNil)
})
Convey("Broken dashboards should not cause error", func() {
cfg := &DashboardsAsConfig{
Name: "Default",
Type: "file",
OrgId: 1,
Folder: "",
Options: map[string]interface{}{
"folder": brokenDashboards,
},
}
_, err := NewDashboardFileReader(cfg, logger)
So(err, ShouldBeNil)
})
})
}
type fakeDashboardRepo struct {
inserted []*dashboards.SaveDashboardItem
getDashboard []*models.Dashboard
}
func (repo *fakeDashboardRepo) SaveDashboard(json *dashboards.SaveDashboardItem) (*models.Dashboard, error) {
repo.inserted = append(repo.inserted, json)
return json.Dashboard, nil
}
func mockGetDashboardQuery(cmd *models.GetDashboardQuery) error {
for _, d := range fakeRepo.getDashboard {
if d.Slug == cmd.Slug {
cmd.Result = d
return nil
}
}
return models.ErrDashboardNotFound
}

View File

@@ -0,0 +1,6 @@
# - name: 'default'
# org_id: 1
# folder: ''
# type: file
# options:
# folder: /var/lib/grafana/dashboards

View File

@@ -0,0 +1,12 @@
- name: 'general dashboards'
org_id: 2
folder: 'developers'
editable: true
type: file
options:
folder: /var/lib/grafana/dashboards
- name: 'default'
type: file
options:
folder: /var/lib/grafana/dashboards

View File

@@ -0,0 +1,6 @@
[]
{
"title": "Grafana",
}

View File

@@ -0,0 +1,173 @@
{
"title": "Grafana",
"tags": [],
"style": "dark",
"timezone": "browser",
"editable": true,
"rows": [
{
"title": "New row",
"height": "150px",
"collapse": false,
"editable": true,
"panels": [
{
"id": 1,
"span": 12,
"editable": true,
"type": "text",
"mode": "html",
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
"style": {},
"title": "Welcome to"
}
]
},
{
"title": "Welcome to Grafana",
"height": "210px",
"collapse": false,
"editable": true,
"panels": [
{
"id": 2,
"span": 6,
"type": "text",
"mode": "html",
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs#configuration\" target=\"_blank\">Configuration</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/troubleshooting\" target=\"_blank\">Troubleshooting</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/support\" target=\"_blank\">Support</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/intro\" target=\"_blank\">Getting started</a> (Must read!)\n </li>\n </ul>\n </div>\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs/features/graphing\" target=\"_blank\">Graphing</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/annotations\" target=\"_blank\">Annotations</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/graphite\" target=\"_blank\">Graphite</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/influxdb\" target=\"_blank\">InfluxDB</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/opentsdb\" target=\"_blank\">OpenTSDB</a>\n </li>\n </ul>\n </div>\n</div>",
"style": {},
"title": "Documentation Links"
},
{
"id": 3,
"span": 6,
"type": "text",
"mode": "html",
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span12\">\n <ul>\n <li>Ctrl+S saves the current dashboard</li>\n <li>Ctrl+F Opens the dashboard finder</li>\n <li>Ctrl+H Hide/show row controls</li>\n <li>Click and drag graph title to move panel</li>\n <li>Hit Escape to exit graph when in fullscreen or edit mode</li>\n <li>Click the colored icon in the legend to change series color</li>\n <li>Ctrl or Shift + Click legend name to hide other series</li>\n </ul>\n </div>\n</div>\n",
"style": {},
"title": "Tips & Shortcuts"
}
]
},
{
"title": "test",
"height": "250px",
"editable": true,
"collapse": false,
"panels": [
{
"id": 4,
"span": 12,
"type": "graph",
"x-axis": true,
"y-axis": true,
"scale": 1,
"y_formats": [
"short",
"short"
],
"grid": {
"max": null,
"min": null,
"leftMax": null,
"rightMax": null,
"leftMin": null,
"rightMin": null,
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"resolution": 100,
"lines": true,
"fill": 1,
"linewidth": 2,
"dashes": false,
"dashLength": 10,
"spaceLength": 10,
"points": false,
"pointradius": 5,
"bars": false,
"stack": true,
"spyable": true,
"options": false,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false
},
"interactive": true,
"legend_counts": true,
"timezone": "browser",
"percentage": false,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"query_as_alias": true
},
"targets": [
{
"target": "randomWalk('random walk')",
"function": "mean",
"column": "value"
}
],
"aliasColors": {},
"aliasYAxis": {},
"title": "First Graph (click title to edit)",
"datasource": "graphite",
"renderer": "flot",
"annotate": {
"enable": false
}
}
]
}
],
"nav": [
{
"type": "timepicker",
"collapse": false,
"enable": true,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"now": true
}
],
"time": {
"from": "now-6h",
"to": "now"
},
"templating": {
"list": []
},
"version": 5
}

View File

@@ -0,0 +1,173 @@
{
"title": "Grafana",
"tags": [],
"style": "dark",
"timezone": "browser",
"editable": true,
"rows": [
{
"title": "New row",
"height": "150px",
"collapse": false,
"editable": true,
"panels": [
{
"id": 1,
"span": 12,
"editable": true,
"type": "text",
"mode": "html",
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
"style": {},
"title": "Welcome to"
}
]
},
{
"title": "Welcome to Grafana",
"height": "210px",
"collapse": false,
"editable": true,
"panels": [
{
"id": 2,
"span": 6,
"type": "text",
"mode": "html",
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs#configuration\" target=\"_blank\">Configuration</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/troubleshooting\" target=\"_blank\">Troubleshooting</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/support\" target=\"_blank\">Support</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/intro\" target=\"_blank\">Getting started</a> (Must read!)\n </li>\n </ul>\n </div>\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs/features/graphing\" target=\"_blank\">Graphing</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/annotations\" target=\"_blank\">Annotations</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/graphite\" target=\"_blank\">Graphite</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/influxdb\" target=\"_blank\">InfluxDB</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/opentsdb\" target=\"_blank\">OpenTSDB</a>\n </li>\n </ul>\n </div>\n</div>",
"style": {},
"title": "Documentation Links"
},
{
"id": 3,
"span": 6,
"type": "text",
"mode": "html",
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span12\">\n <ul>\n <li>Ctrl+S saves the current dashboard</li>\n <li>Ctrl+F Opens the dashboard finder</li>\n <li>Ctrl+H Hide/show row controls</li>\n <li>Click and drag graph title to move panel</li>\n <li>Hit Escape to exit graph when in fullscreen or edit mode</li>\n <li>Click the colored icon in the legend to change series color</li>\n <li>Ctrl or Shift + Click legend name to hide other series</li>\n </ul>\n </div>\n</div>\n",
"style": {},
"title": "Tips & Shortcuts"
}
]
},
{
"title": "test",
"height": "250px",
"editable": true,
"collapse": false,
"panels": [
{
"id": 4,
"span": 12,
"type": "graph",
"x-axis": true,
"y-axis": true,
"scale": 1,
"y_formats": [
"short",
"short"
],
"grid": {
"max": null,
"min": null,
"leftMax": null,
"rightMax": null,
"leftMin": null,
"rightMin": null,
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"resolution": 100,
"lines": true,
"fill": 1,
"linewidth": 2,
"dashes": false,
"dashLength": 10,
"spaceLength": 10,
"points": false,
"pointradius": 5,
"bars": false,
"stack": true,
"spyable": true,
"options": false,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false
},
"interactive": true,
"legend_counts": true,
"timezone": "browser",
"percentage": false,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"query_as_alias": true
},
"targets": [
{
"target": "randomWalk('random walk')",
"function": "mean",
"column": "value"
}
],
"aliasColors": {},
"aliasYAxis": {},
"title": "First Graph (click title to edit)",
"datasource": "graphite",
"renderer": "flot",
"annotate": {
"enable": false
}
}
]
}
],
"nav": [
{
"type": "timepicker",
"collapse": false,
"enable": true,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"now": true
}
],
"time": {
"from": "now-6h",
"to": "now"
},
"templating": {
"list": []
},
"version": 5
}

View File

@@ -0,0 +1,173 @@
{
"title": "Grafana",
"tags": [],
"style": "dark",
"timezone": "browser",
"editable": true,
"rows": [
{
"title": "New row",
"height": "150px",
"collapse": false,
"editable": true,
"panels": [
{
"id": 1,
"span": 12,
"editable": true,
"type": "text",
"mode": "html",
"content": "<div class=\"text-center\" style=\"padding-top: 15px\">\n<img src=\"img/logo_transparent_200x.png\"> \n</div>",
"style": {},
"title": "Welcome to"
}
]
},
{
"title": "Welcome to Grafana",
"height": "210px",
"collapse": false,
"editable": true,
"panels": [
{
"id": 2,
"span": 6,
"type": "text",
"mode": "html",
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs#configuration\" target=\"_blank\">Configuration</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/troubleshooting\" target=\"_blank\">Troubleshooting</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/support\" target=\"_blank\">Support</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/intro\" target=\"_blank\">Getting started</a> (Must read!)\n </li>\n </ul>\n </div>\n <div class=\"span6\">\n <ul>\n <li>\n <a href=\"http://grafana.org/docs/features/graphing\" target=\"_blank\">Graphing</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/annotations\" target=\"_blank\">Annotations</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/graphite\" target=\"_blank\">Graphite</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/influxdb\" target=\"_blank\">InfluxDB</a>\n </li>\n <li>\n <a href=\"http://grafana.org/docs/features/opentsdb\" target=\"_blank\">OpenTSDB</a>\n </li>\n </ul>\n </div>\n</div>",
"style": {},
"title": "Documentation Links"
},
{
"id": 3,
"span": 6,
"type": "text",
"mode": "html",
"content": "<br/>\n\n<div class=\"row-fluid\">\n <div class=\"span12\">\n <ul>\n <li>Ctrl+S saves the current dashboard</li>\n <li>Ctrl+F Opens the dashboard finder</li>\n <li>Ctrl+H Hide/show row controls</li>\n <li>Click and drag graph title to move panel</li>\n <li>Hit Escape to exit graph when in fullscreen or edit mode</li>\n <li>Click the colored icon in the legend to change series color</li>\n <li>Ctrl or Shift + Click legend name to hide other series</li>\n </ul>\n </div>\n</div>\n",
"style": {},
"title": "Tips & Shortcuts"
}
]
},
{
"title": "test",
"height": "250px",
"editable": true,
"collapse": false,
"panels": [
{
"id": 4,
"span": 12,
"type": "graph",
"x-axis": true,
"y-axis": true,
"scale": 1,
"y_formats": [
"short",
"short"
],
"grid": {
"max": null,
"min": null,
"leftMax": null,
"rightMax": null,
"leftMin": null,
"rightMin": null,
"threshold1": null,
"threshold2": null,
"threshold1Color": "rgba(216, 200, 27, 0.27)",
"threshold2Color": "rgba(234, 112, 112, 0.22)"
},
"resolution": 100,
"lines": true,
"fill": 1,
"linewidth": 2,
"dashes": false,
"dashLength": 10,
"spaceLength": 10,
"points": false,
"pointradius": 5,
"bars": false,
"stack": true,
"spyable": true,
"options": false,
"legend": {
"show": true,
"values": false,
"min": false,
"max": false,
"current": false,
"total": false,
"avg": false
},
"interactive": true,
"legend_counts": true,
"timezone": "browser",
"percentage": false,
"nullPointMode": "connected",
"steppedLine": false,
"tooltip": {
"value_type": "cumulative",
"query_as_alias": true
},
"targets": [
{
"target": "randomWalk('random walk')",
"function": "mean",
"column": "value"
}
],
"aliasColors": {},
"aliasYAxis": {},
"title": "First Graph (click title to edit)",
"datasource": "graphite",
"renderer": "flot",
"annotate": {
"enable": false
}
}
]
}
],
"nav": [
{
"type": "timepicker",
"collapse": false,
"enable": true,
"status": "Stable",
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
],
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"now": true
}
],
"time": {
"from": "now-6h",
"to": "now"
},
"templating": {
"list": []
},
"version": 5
}

View File

@@ -0,0 +1,38 @@
package dashboards
import (
"strings"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/models"
)
type DashboardsAsConfig struct {
Name string `json:"name" yaml:"name"`
Type string `json:"type" yaml:"type"`
OrgId int64 `json:"org_id" yaml:"org_id"`
Folder string `json:"folder" yaml:"folder"`
Editable bool `json:"editable" yaml:"editable"`
Options map[string]interface{} `json:"options" yaml:"options"`
}
func createDashboardJson(data *simplejson.Json, lastModified time.Time, cfg *DashboardsAsConfig) (*dashboards.SaveDashboardItem, error) {
dash := &dashboards.SaveDashboardItem{}
dash.Dashboard = models.NewDashboardFromJson(data)
dash.TitleLower = strings.ToLower(dash.Dashboard.Title)
dash.UpdatedAt = lastModified
dash.Overwrite = true
dash.OrgId = cfg.OrgId
dash.Folder = cfg.Folder
dash.Dashboard.Data.Set("editable", cfg.Editable)
if dash.Dashboard.Title == "" {
return nil, models.ErrDashboardTitleEmpty
}
return dash, nil
}

View File

@@ -118,13 +118,19 @@ func (configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
return nil, err
}
datasources = append(datasources, datasource)
if datasource != nil {
datasources = append(datasources, datasource)
}
}
}
defaultCount := 0
for _, cfg := range datasources {
for _, ds := range cfg.Datasources {
for i := range datasources {
if datasources[i].Datasources == nil {
continue
}
for _, ds := range datasources[i].Datasources {
if ds.OrgId == 0 {
ds.OrgId = 1
}
@@ -137,7 +143,7 @@ func (configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
}
}
for _, ds := range cfg.DeleteDatasources {
for _, ds := range datasources[i].DeleteDatasources {
if ds.OrgId == 0 {
ds.OrgId = 1
}

View File

@@ -0,0 +1,48 @@
# # list of datasources that should be deleted from the database
#delete_datasources:
# - name: Graphite
# org_id: 1
# # list of datasources to insert/update depending
# # whats available in the datbase
#datasources:
# # <string, required> name of the datasource. Required
# - name: Graphite
# # <string, required> datasource type. Required
# type: graphite
# # <string, required> access mode. direct or proxy. Required
# access: proxy
# # <int> org id. will default to org_id 1 if not specified
# org_id: 1
# # <string> url
# url: http://localhost:8080
# # <string> database password, if used
# password:
# # <string> database user, if used
# user:
# # <string> database name, if used
# database:
# # <bool> enable/disable basic auth
# basic_auth:
# # <string> basic auth username
# basic_auth_user:
# # <string> basic auth password
# basic_auth_password:
# # <bool> enable/disable with credentials headers
# with_credentials:
# # <bool> mark as default datasource. Max one per org
# is_default:
# # <map> fields that will be converted to json and stored in json_data
# json_data:
# graphiteVersion: "1.1"
# tlsAuth: true
# tlsAuthWithCACert: true
# # <string> json object of data that will be encrypted.
# secure_json_data:
# tlsCACert: "..."
# tlsClientCert: "..."
# tlsClientKey: "..."
# version: 1
# # <bool> allow users to edit datasources from the UI.
# editable: false

View File

@@ -1,14 +1,35 @@
package provisioning
import (
"github.com/grafana/grafana/pkg/log"
"context"
"path"
"path/filepath"
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
"github.com/grafana/grafana/pkg/services/provisioning/datasources"
ini "gopkg.in/ini.v1"
)
var (
logger log.Logger = log.New("services.provisioning")
)
func Init(ctx context.Context, homePath string, cfg *ini.File) error {
provisioningPath := makeAbsolute(cfg.Section("paths").Key("provisioning").String(), homePath)
func StartUp(datasourcePath string) error {
return datasources.Provision(datasourcePath)
datasourcePath := path.Join(provisioningPath, "datasources")
if err := datasources.Provision(datasourcePath); err != nil {
return err
}
dashboardPath := path.Join(provisioningPath, "dashboards")
_, err := dashboards.Provision(ctx, dashboardPath)
if err != nil {
return err
}
return nil
}
func makeAbsolute(path string, root string) string {
if filepath.IsAbs(path) {
return path
}
return filepath.Join(root, path)
}

View File

@@ -12,6 +12,7 @@ func TestSearch(t *testing.T) {
Convey("Given search query", t, func() {
query := Query{Limit: 2000, SignedInUser: &m.SignedInUser{IsGrafanaAdmin: true}}
bus.AddHandler("test", func(query *FindPersistedDashboardsQuery) error {
query.Result = HitList{
&Hit{Id: 16, Title: "CCAA", Type: "dash-db", Tags: []string{"BB", "AA"}},

View File

@@ -84,6 +84,11 @@ func SaveDashboard(cmd *m.SaveDashboardCommand) error {
} else {
dash.Version++
dash.Data.Set("version", dash.Version)
if !cmd.UpdatedAt.IsZero() {
dash.Updated = cmd.UpdatedAt
}
affectedRows, err = sess.MustCols("folder_id", "has_acl").Id(dash.Id).Update(dash)
}

View File

@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/annotations"
"github.com/grafana/grafana/pkg/services/dashboards"
"github.com/grafana/grafana/pkg/services/sqlstore/migrations"
"github.com/grafana/grafana/pkg/services/sqlstore/migrator"
"github.com/grafana/grafana/pkg/setting"
@@ -100,6 +101,7 @@ func SetEngine(engine *xorm.Engine) (err error) {
// Init repo instances
annotations.SetRepository(&SqlAnnotationRepo{})
dashboards.SetRepository(&dashboards.DashboardRepository{})
return nil
}

View File

@@ -50,12 +50,12 @@ var (
BuildStamp int64
// Paths
LogsPath string
HomePath string
DataPath string
PluginsPath string
DatasourcesPath string
CustomInitPath = "conf/custom.ini"
LogsPath string
HomePath string
DataPath string
PluginsPath string
ProvisioningPath string
CustomInitPath = "conf/custom.ini"
// Log settings.
LogModes []string
@@ -474,8 +474,7 @@ func NewConfigContext(args *CommandLineArgs) error {
Env = Cfg.Section("").Key("app_mode").MustString("development")
InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name")
PluginsPath = makeAbsolute(Cfg.Section("paths").Key("plugins").String(), HomePath)
DatasourcesPath = makeAbsolute(Cfg.Section("paths").Key("datasources").String(), HomePath)
ProvisioningPath = makeAbsolute(Cfg.Section("paths").Key("provisioning").String(), HomePath)
server := Cfg.Section("server")
AppUrl, AppSubUrl = parseAppUrlAndSubUrl(server)
@@ -670,6 +669,6 @@ func LogConfigurationInfo() {
logger.Info("Path Data", "path", DataPath)
logger.Info("Path Logs", "path", LogsPath)
logger.Info("Path Plugins", "path", PluginsPath)
logger.Info("Path Datasources", "path", DatasourcesPath)
logger.Info("Path Provisioning", "path", ProvisioningPath)
logger.Info("App mode " + Env)
}

View File

@@ -58,12 +58,12 @@ func (s *SocialGithub) IsTeamMember(client *http.Client) bool {
return false
}
func (s *SocialGithub) IsOrganizationMember(client *http.Client) bool {
func (s *SocialGithub) IsOrganizationMember(client *http.Client, organizationsUrl string) bool {
if len(s.allowedOrganizations) == 0 {
return true
}
organizations, err := s.FetchOrganizations(client)
organizations, err := s.FetchOrganizations(client, organizationsUrl)
if err != nil {
return false
}
@@ -167,12 +167,12 @@ func (s *SocialGithub) HasMoreRecords(headers http.Header) (string, bool) {
}
func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error) {
func (s *SocialGithub) FetchOrganizations(client *http.Client, organizationsUrl string) ([]string, error) {
type Record struct {
Login string `json:"login"`
}
response, err := HttpGet(client, fmt.Sprintf(s.apiUrl+"/orgs"))
response, err := HttpGet(client, organizationsUrl)
if err != nil {
return nil, fmt.Errorf("Error getting organizations: %s", err)
}
@@ -193,10 +193,12 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client) ([]string, error)
}
func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
var data struct {
Id int `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
Id int `json:"id"`
Login string `json:"login"`
Email string `json:"email"`
OrganizationsUrl string `json:"organizations_url"`
}
response, err := HttpGet(client, s.apiUrl)
@@ -219,7 +221,7 @@ func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
return nil, ErrMissingTeamMembership
}
if !s.IsOrganizationMember(client) {
if !s.IsOrganizationMember(client, data.OrganizationsUrl) {
return nil, ErrMissingOrganizationMembership
}

View File

@@ -11,6 +11,7 @@ import (
"github.com/aws/aws-sdk-go/aws/credentials"
"github.com/aws/aws-sdk-go/aws/credentials/ec2rolecreds"
"github.com/aws/aws-sdk-go/aws/credentials/endpointcreds"
"github.com/aws/aws-sdk-go/aws/defaults"
"github.com/aws/aws-sdk-go/aws/ec2metadata"
"github.com/aws/aws-sdk-go/aws/session"
"github.com/aws/aws-sdk-go/service/cloudwatch"
@@ -128,10 +129,10 @@ func remoteCredProvider(sess *session.Session) credentials.Provider {
func ecsCredProvider(sess *session.Session, uri string) credentials.Provider {
const host = `169.254.170.2`
c := ec2metadata.New(sess)
d := defaults.Get()
return endpointcreds.NewProviderClient(
c.Client.Config,
c.Client.Handlers,
*d.Config,
d.Handlers,
fmt.Sprintf("http://%s%s", host, uri),
func(p *endpointcreds.Provider) { p.ExpiryWindow = 5 * time.Minute })
}
@@ -141,6 +142,11 @@ func ec2RoleProvider(sess *session.Session) credentials.Provider {
}
func (e *CloudWatchExecutor) getDsInfo(region string) *DatasourceInfo {
defaultRegion := e.DataSource.JsonData.Get("defaultRegion").MustString()
if region == "default" {
region = defaultRegion
}
authType := e.DataSource.JsonData.Get("authType").MustString()
assumeRoleArn := e.DataSource.JsonData.Get("assumeRoleArn").MustString()
accessKey := ""

View File

@@ -89,7 +89,7 @@ func (m *PostgresMacroEngine) evaluateMacro(name string, args []string) (string,
if err != nil {
return "", fmt.Errorf("error parsing interval %v", args[1])
}
return fmt.Sprintf("(extract(epoch from \"%s\")/%v)::bigint*%v", args[0], interval.Seconds(), interval.Seconds()), nil
return fmt.Sprintf("(extract(epoch from %s)/%v)::bigint*%v AS time", args[0], interval.Seconds(), interval.Seconds()), nil
case "__unixEpochFilter":
if len(args) == 0 {
return "", fmt.Errorf("missing time column argument for macro %v", name)

View File

@@ -45,7 +45,7 @@ func TestMacroEngine(t *testing.T) {
sql, err := engine.Interpolate(timeRange, "GROUP BY $__timeGroup(time_column,'5m')")
So(err, ShouldBeNil)
So(sql, ShouldEqual, "GROUP BY (extract(epoch from \"time_column\")/300)::bigint*300")
So(sql, ShouldEqual, "GROUP BY (extract(epoch from time_column)/300)::bigint*300 AS time")
})
Convey("interpolate __timeTo function", func() {