Merge branch 'master' into readonly_dashboards

This commit is contained in:
Marcus Efraimsson
2018-04-05 20:30:27 +02:00
94 changed files with 1681 additions and 769 deletions
+4
View File
@@ -50,6 +50,10 @@ type UserStars struct {
}
func GetGravatarUrl(text string) string {
if setting.DisableGravatar {
return "/public/img/user_profile.png"
}
if text == "" {
return ""
}
+1 -1
View File
@@ -111,7 +111,7 @@ func (g *GrafanaServerImpl) initLogging() {
})
if err != nil {
g.log.Error(err.Error())
fmt.Fprintf(os.Stderr, "Failed to start grafana. error: %s\n", err.Error())
os.Exit(1)
}
+14 -18
View File
@@ -1,8 +1,8 @@
package middleware
import (
"errors"
"fmt"
"net"
"strings"
"time"
@@ -25,7 +25,7 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
}
// if auth proxy ip(s) defined, check if request comes from one of those
if err := checkAuthenticationProxy(ctx, proxyHeaderValue); err != nil {
if err := checkAuthenticationProxy(ctx.Req.RemoteAddr, proxyHeaderValue); err != nil {
ctx.Handle(407, "Proxy authentication required", err)
return true
}
@@ -123,29 +123,25 @@ var syncGrafanaUserWithLdapUser = func(ctx *m.ReqContext, query *m.GetSignedInUs
return nil
}
func checkAuthenticationProxy(ctx *m.ReqContext, proxyHeaderValue string) error {
func checkAuthenticationProxy(remoteAddr string, proxyHeaderValue string) error {
if len(strings.TrimSpace(setting.AuthProxyWhitelist)) == 0 {
return nil
}
proxies := strings.Split(setting.AuthProxyWhitelist, ",")
remoteAddrSplit := strings.Split(ctx.Req.RemoteAddr, ":")
sourceIP := remoteAddrSplit[0]
found := false
for _, proxyIP := range proxies {
if sourceIP == strings.TrimSpace(proxyIP) {
found = true
break
}
}
if !found {
msg := fmt.Sprintf("Request for user (%s) is not from the authentication proxy", proxyHeaderValue)
err := errors.New(msg)
sourceIP, _, err := net.SplitHostPort(remoteAddr)
if err != nil {
return err
}
return nil
// Compare allowed IP addresses to actual address
for _, proxyIP := range proxies {
if sourceIP == strings.TrimSpace(proxyIP) {
return nil
}
}
return fmt.Errorf("Request for user (%s) from %s is not from the authentication proxy", proxyHeaderValue, sourceIP)
}
func getSignedInUserQueryForProxyAuth(headerVal string) *m.GetSignedInUserQuery {
+22 -4
View File
@@ -226,11 +226,11 @@ func TestMiddlewareContext(t *testing.T) {
})
})
middlewareScenario("When auth_proxy is enabled and request RemoteAddr is not trusted", func(sc *scenarioContext) {
middlewareScenario("When auth_proxy is enabled and IPv4 request RemoteAddr is not trusted", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 192.168.2.1"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
@@ -239,6 +239,24 @@ func TestMiddlewareContext(t *testing.T) {
Convey("should return 407 status code", func() {
So(sc.resp.Code, ShouldEqual, 407)
So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 192.168.3.1 is not from the authentication proxy")
})
})
middlewareScenario("When auth_proxy is enabled and IPv6 request RemoteAddr is not trusted", func(sc *scenarioContext) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.req.RemoteAddr = "[2001:23]:12345"
sc.exec()
Convey("should return 407 status code", func() {
So(sc.resp.Code, ShouldEqual, 407)
So(sc.resp.Body.String(), ShouldContainSubstring, "Request for user (torkelo) from 2001:23 is not from the authentication proxy")
})
})
@@ -246,7 +264,7 @@ func TestMiddlewareContext(t *testing.T) {
setting.AuthProxyEnabled = true
setting.AuthProxyHeaderName = "X-WEBAUTH-USER"
setting.AuthProxyHeaderProperty = "username"
setting.AuthProxyWhitelist = "192.168.1.1, 192.168.2.1"
setting.AuthProxyWhitelist = "192.168.1.1, 2001::23"
bus.AddHandler("test", func(query *m.GetSignedInUserQuery) error {
query.Result = &m.SignedInUser{OrgId: 4, UserId: 33}
@@ -255,7 +273,7 @@ func TestMiddlewareContext(t *testing.T) {
sc.fakeReq("GET", "/")
sc.req.Header.Add("X-WEBAUTH-USER", "torkelo")
sc.req.RemoteAddr = "192.168.2.1:12345"
sc.req.RemoteAddr = "[2001::23]:12345"
sc.exec()
Convey("Should init context with user info", func() {
+3 -3
View File
@@ -211,14 +211,14 @@ func GetDashboardFolderUrl(isFolder bool, uid string, slug string) string {
return GetDashboardUrl(uid, slug)
}
// Return the html url for a dashboard
// GetDashboardUrl return the html url for a dashboard
func GetDashboardUrl(uid string, slug string) string {
return fmt.Sprintf("%s/d/%s/%s", setting.AppSubUrl, uid, slug)
}
// Return the full url for a dashboard
// GetFullDashboardUrl return the full url for a dashboard
func GetFullDashboardUrl(uid string, slug string) string {
return fmt.Sprintf("%s%s", setting.AppUrl, GetDashboardUrl(uid, slug))
return fmt.Sprintf("%sd/%s/%s", setting.AppUrl, uid, slug)
}
// GetFolderUrl return the html url for a folder
+13
View File
@@ -4,11 +4,24 @@ import (
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/setting"
. "github.com/smartystreets/goconvey/convey"
)
func TestDashboardModel(t *testing.T) {
Convey("Generate full dashboard url", t, func() {
setting.AppUrl = "http://grafana.local/"
fullUrl := GetFullDashboardUrl("uid", "my-dashboard")
So(fullUrl, ShouldEqual, "http://grafana.local/d/uid/my-dashboard")
})
Convey("Generate relative dashboard url", t, func() {
setting.AppUrl = ""
fullUrl := GetDashboardUrl("uid", "my-dashboard")
So(fullUrl, ShouldEqual, "/d/uid/my-dashboard")
})
Convey("When generating slug", t, func() {
dashboard := NewDashboard("Grafana Play Home")
dashboard.UpdateSlug()
+6 -13
View File
@@ -13,11 +13,7 @@ func init() {
func validateDashboardAlerts(cmd *m.ValidateDashboardAlertsCommand) error {
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
if _, err := extractor.GetAlerts(); err != nil {
return err
}
return nil
return extractor.ValidateAlerts()
}
func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
@@ -29,15 +25,12 @@ func updateDashboardAlerts(cmd *m.UpdateDashboardAlertsCommand) error {
extractor := NewDashAlertExtractor(cmd.Dashboard, cmd.OrgId)
if alerts, err := extractor.GetAlerts(); err != nil {
return err
} else {
saveAlerts.Alerts = alerts
}
if err := bus.Dispatch(&saveAlerts); err != nil {
alerts, err := extractor.GetAlerts()
if err != nil {
return err
}
return nil
saveAlerts.Alerts = alerts
return bus.Dispatch(&saveAlerts)
}
+57 -40
View File
@@ -11,76 +11,78 @@ import (
m "github.com/grafana/grafana/pkg/models"
)
// DashAlertExtractor extracts alerts from the dashboard json
type DashAlertExtractor struct {
Dash *m.Dashboard
OrgId int64
OrgID int64
log log.Logger
}
func NewDashAlertExtractor(dash *m.Dashboard, orgId int64) *DashAlertExtractor {
// NewDashAlertExtractor returns a new DashAlertExtractor
func NewDashAlertExtractor(dash *m.Dashboard, orgID int64) *DashAlertExtractor {
return &DashAlertExtractor{
Dash: dash,
OrgId: orgId,
OrgID: orgID,
log: log.New("alerting.extractor"),
}
}
func (e *DashAlertExtractor) lookupDatasourceId(dsName string) (*m.DataSource, error) {
func (e *DashAlertExtractor) lookupDatasourceID(dsName string) (*m.DataSource, error) {
if dsName == "" {
query := &m.GetDataSourcesQuery{OrgId: e.OrgId}
query := &m.GetDataSourcesQuery{OrgId: e.OrgID}
if err := bus.Dispatch(query); err != nil {
return nil, err
} else {
for _, ds := range query.Result {
if ds.IsDefault {
return ds, nil
}
}
for _, ds := range query.Result {
if ds.IsDefault {
return ds, nil
}
}
} else {
query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgId}
query := &m.GetDataSourceByNameQuery{Name: dsName, OrgId: e.OrgID}
if err := bus.Dispatch(query); err != nil {
return nil, err
} else {
return query.Result, nil
}
return query.Result, nil
}
return nil, errors.New("Could not find datasource id for " + dsName)
}
func findPanelQueryByRefId(panel *simplejson.Json, refId string) *simplejson.Json {
func findPanelQueryByRefID(panel *simplejson.Json, refID string) *simplejson.Json {
for _, targetsObj := range panel.Get("targets").MustArray() {
target := simplejson.NewFromAny(targetsObj)
if target.Get("refId").MustString() == refId {
if target.Get("refId").MustString() == refID {
return target
}
}
return nil
}
func copyJson(in *simplejson.Json) (*simplejson.Json, error) {
rawJson, err := in.MarshalJSON()
func copyJSON(in *simplejson.Json) (*simplejson.Json, error) {
rawJSON, err := in.MarshalJSON()
if err != nil {
return nil, err
}
return simplejson.NewJson(rawJson)
return simplejson.NewJson(rawJSON)
}
func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json) ([]*m.Alert, error) {
func (e *DashAlertExtractor) getAlertFromPanels(jsonWithPanels *simplejson.Json, validateAlertFunc func(*m.Alert) bool) ([]*m.Alert, error) {
alerts := make([]*m.Alert, 0)
for _, panelObj := range jsonWithPanels.Get("panels").MustArray() {
panel := simplejson.NewFromAny(panelObj)
collapsedJson, collapsed := panel.CheckGet("collapsed")
collapsedJSON, collapsed := panel.CheckGet("collapsed")
// check if the panel is collapsed
if collapsed && collapsedJson.MustBool() {
if collapsed && collapsedJSON.MustBool() {
// extract alerts from sub panels for collapsed panels
als, err := e.GetAlertFromPanels(panel)
als, err := e.getAlertFromPanels(panel, validateAlertFunc)
if err != nil {
return nil, err
}
@@ -95,7 +97,7 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
continue
}
panelId, err := panel.Get("id").Int64()
panelID, err := panel.Get("id").Int64()
if err != nil {
return nil, fmt.Errorf("panel id is required. err %v", err)
}
@@ -113,8 +115,8 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
alert := &m.Alert{
DashboardId: e.Dash.Id,
OrgId: e.OrgId,
PanelId: panelId,
OrgId: e.OrgID,
PanelId: panelID,
Id: jsonAlert.Get("id").MustInt64(),
Name: jsonAlert.Get("name").MustString(),
Handler: jsonAlert.Get("handler").MustInt64(),
@@ -126,11 +128,11 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
jsonCondition := simplejson.NewFromAny(condition)
jsonQuery := jsonCondition.Get("query")
queryRefId := jsonQuery.Get("params").MustArray()[0].(string)
panelQuery := findPanelQueryByRefId(panel, queryRefId)
queryRefID := jsonQuery.Get("params").MustArray()[0].(string)
panelQuery := findPanelQueryByRefID(panel, queryRefID)
if panelQuery == nil {
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefId)
reason := fmt.Sprintf("Alert on PanelId: %v refers to query(%s) that cannot be found", alert.PanelId, queryRefID)
return nil, ValidationError{Reason: reason}
}
@@ -141,12 +143,13 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
dsName = panel.Get("datasource").MustString()
}
if datasource, err := e.lookupDatasourceId(dsName); err != nil {
datasource, err := e.lookupDatasourceID(dsName)
if err != nil {
return nil, err
} else {
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
}
jsonQuery.SetPath([]string{"datasourceId"}, datasource.Id)
if interval, err := panel.Get("interval").String(); err == nil {
panelQuery.Set("interval", interval)
}
@@ -162,21 +165,28 @@ func (e *DashAlertExtractor) GetAlertFromPanels(jsonWithPanels *simplejson.Json)
return nil, err
}
if alert.ValidToSave() {
alerts = append(alerts, alert)
} else {
if !validateAlertFunc(alert) {
e.log.Debug("Invalid Alert Data. Dashboard, Org or Panel ID is not correct", "alertName", alert.Name, "panelId", alert.PanelId)
return nil, m.ErrDashboardContainsInvalidAlertData
}
alerts = append(alerts, alert)
}
return alerts, nil
}
func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
e.log.Debug("GetAlerts")
func validateAlertRule(alert *m.Alert) bool {
return alert.ValidToSave()
}
dashboardJson, err := copyJson(e.Dash.Data)
// GetAlerts extracts alerts from the dashboard json and does full validation on the alert json data
func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
return e.extractAlerts(validateAlertRule)
}
func (e *DashAlertExtractor) extractAlerts(validateFunc func(alert *m.Alert) bool) ([]*m.Alert, error) {
dashboardJSON, err := copyJSON(e.Dash.Data)
if err != nil {
return nil, err
}
@@ -185,11 +195,11 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
// We extract alerts from rows to be backwards compatible
// with the old dashboard json model.
rows := dashboardJson.Get("rows").MustArray()
rows := dashboardJSON.Get("rows").MustArray()
if len(rows) > 0 {
for _, rowObj := range rows {
row := simplejson.NewFromAny(rowObj)
a, err := e.GetAlertFromPanels(row)
a, err := e.getAlertFromPanels(row, validateFunc)
if err != nil {
return nil, err
}
@@ -197,7 +207,7 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
alerts = append(alerts, a...)
}
} else {
a, err := e.GetAlertFromPanels(dashboardJson)
a, err := e.getAlertFromPanels(dashboardJSON, validateFunc)
if err != nil {
return nil, err
}
@@ -208,3 +218,10 @@ func (e *DashAlertExtractor) GetAlerts() ([]*m.Alert, error) {
e.log.Debug("Extracted alerts from dashboard", "alertCount", len(alerts))
return alerts, nil
}
// ValidateAlerts validates alerts in the dashboard json but does not require a valid dashboard id
// in the first validation pass
func (e *DashAlertExtractor) ValidateAlerts() error {
_, err := e.extractAlerts(func(alert *m.Alert) bool { return alert.OrgId != 0 && alert.PanelId != 0 })
return err
}
+21
View File
@@ -240,5 +240,26 @@ func TestAlertRuleExtraction(t *testing.T) {
So(len(alerts), ShouldEqual, 4)
})
})
Convey("Parse and validate dashboard without id and containing an alert", func() {
json, err := ioutil.ReadFile("./test-data/dash-without-id.json")
So(err, ShouldBeNil)
dashJSON, err := simplejson.NewJson(json)
So(err, ShouldBeNil)
dash := m.NewDashboardFromJson(dashJSON)
extractor := NewDashAlertExtractor(dash, 1)
err = extractor.ValidateAlerts()
Convey("Should validate without error", func() {
So(err, ShouldBeNil)
})
Convey("Should fail on save", func() {
_, err := extractor.GetAlerts()
So(err, ShouldEqual, m.ErrDashboardContainsInvalidAlertData)
})
})
})
}
+5 -1
View File
@@ -15,7 +15,11 @@ type NotifierBase struct {
}
func NewNotifierBase(id int64, isDefault bool, name, notifierType string, model *simplejson.Json) NotifierBase {
uploadImage := model.Get("uploadImage").MustBool(false)
uploadImage := true
value, exist := model.CheckGet("uploadImage")
if exist {
uploadImage = value.MustBool()
}
return NotifierBase{
Id: id,
@@ -4,6 +4,7 @@ import (
"context"
"testing"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
. "github.com/smartystreets/goconvey/convey"
@@ -11,6 +12,29 @@ import (
func TestBaseNotifier(t *testing.T) {
Convey("Base notifier tests", t, func() {
Convey("default constructor for notifiers", func() {
bJson := simplejson.New()
Convey("can parse false value", func() {
bJson.Set("uploadImage", false)
base := NewNotifierBase(1, false, "name", "email", bJson)
So(base.UploadImage, ShouldBeFalse)
})
Convey("can parse true value", func() {
bJson.Set("uploadImage", true)
base := NewNotifierBase(1, false, "name", "email", bJson)
So(base.UploadImage, ShouldBeTrue)
})
Convey("default value should be true for backwards compatibility", func() {
base := NewNotifierBase(1, false, "name", "email", bJson)
So(base.UploadImage, ShouldBeTrue)
})
})
Convey("should notify", func() {
Convey("pending -> ok", func() {
context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
@@ -0,0 +1,281 @@
{
"title": "Influxdb",
"tags": [
"apa"
],
"style": "dark",
"timezone": "browser",
"editable": true,
"hideControls": false,
"sharedCrosshair": false,
"rows": [
{
"collapse": false,
"editable": true,
"height": "450px",
"panels": [
{
"alert": {
"conditions": [
{
"evaluator": {
"params": [
10
],
"type": "gt"
},
"query": {
"params": [
"B",
"5m",
"now"
]
},
"reducer": {
"params": [],
"type": "avg"
},
"type": "query"
}
],
"frequency": "3s",
"handler": 1,
"name": "Influxdb",
"noDataState": "no_data",
"notifications": [
{
"id": 6
}
]
},
"alerting": {},
"aliasColors": {
"logins.count.count": "#890F02"
},
"bars": false,
"datasource": "InfluxDB",
"editable": true,
"error": false,
"fill": 1,
"grid": {},
"id": 1,
"interval": ">10s",
"isNew": true,
"legend": {
"avg": false,
"current": false,
"max": false,
"min": false,
"show": true,
"total": false,
"values": false
},
"lines": true,
"linewidth": 2,
"links": [],
"nullPointMode": "connected",
"percentage": false,
"pointradius": 5,
"points": false,
"renderer": "flot",
"seriesOverrides": [],
"span": 10,
"stack": false,
"steppedLine": false,
"targets": [
{
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"datacenter"
],
"type": "tag"
},
{
"params": [
"none"
],
"type": "fill"
}
],
"hide": false,
"measurement": "logins.count",
"policy": "default",
"query": "SELECT 8 * count(\"value\") FROM \"logins.count\" WHERE $timeFilter GROUP BY time($interval), \"datacenter\" fill(none)",
"rawQuery": true,
"refId": "B",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "count"
}
]
],
"tags": []
},
{
"groupBy": [
{
"params": [
"$interval"
],
"type": "time"
},
{
"params": [
"null"
],
"type": "fill"
}
],
"hide": true,
"measurement": "cpu",
"policy": "default",
"refId": "A",
"resultFormat": "time_series",
"select": [
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "mean"
}
],
[
{
"params": [
"value"
],
"type": "field"
},
{
"params": [],
"type": "sum"
}
]
],
"tags": []
}
],
"thresholds": [
{
"colorMode": "critical",
"fill": true,
"line": true,
"op": "gt",
"value": 10
}
],
"timeFrom": null,
"timeShift": null,
"title": "Panel Title",
"tooltip": {
"msResolution": false,
"ordering": "alphabetical",
"shared": true,
"sort": 0,
"value_type": "cumulative"
},
"type": "graph",
"xaxis": {
"mode": "time",
"name": null,
"show": true,
"values": []
},
"yaxes": [
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
},
{
"format": "short",
"logBase": 1,
"max": null,
"min": null,
"show": true
}
]
},
{
"editable": true,
"error": false,
"id": 2,
"isNew": true,
"limit": 10,
"links": [],
"show": "current",
"span": 2,
"stateFilter": [
"alerting"
],
"title": "Alert status",
"type": "alertlist"
}
],
"title": "Row"
}
],
"time": {
"from": "now-5m",
"to": "now"
},
"timepicker": {
"now": true,
"refresh_intervals": [
"5s",
"10s",
"30s",
"1m",
"5m",
"15m",
"30m",
"1h",
"2h",
"1d"
],
"time_options": [
"5m",
"15m",
"1h",
"6h",
"12h",
"24h",
"2d",
"7d",
"30d"
]
},
"templating": {
"list": []
},
"annotations": {
"list": []
},
"schemaVersion": 13,
"version": 120,
"links": [],
"gnetId": null
}
@@ -279,4 +279,4 @@
"version": 120,
"links": [],
"gnetId": null
}
}
+1 -1
View File
@@ -17,7 +17,7 @@ import (
"github.com/grafana/grafana/pkg/log"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/setting"
"gopkg.in/gomail.v2"
gomail "gopkg.in/mail.v2"
)
var mailQueue chan *Message
@@ -170,8 +170,8 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil
}
if dash.Dashboard.Id != 0 {
fr.log.Error("provisioned dashboard json files cannot contain id")
return provisioningMetadata, nil
dash.Dashboard.Data.Set("id", nil)
dash.Dashboard.Id = 0
}
if alreadyProvisioned {
@@ -15,9 +15,10 @@ import (
)
var (
defaultDashboards string = "./test-dashboards/folder-one"
brokenDashboards string = "./test-dashboards/broken-dashboards"
oneDashboard string = "./test-dashboards/one-dashboard"
defaultDashboards = "./test-dashboards/folder-one"
brokenDashboards = "./test-dashboards/broken-dashboards"
oneDashboard = "./test-dashboards/one-dashboard"
containingId = "./test-dashboards/containing-id"
fakeService *fakeDashboardProvisioningService
)
@@ -85,6 +86,18 @@ func TestDashboardFileReader(t *testing.T) {
So(len(fakeService.inserted), ShouldEqual, 1)
})
Convey("Overrides id from dashboard.json files", func() {
cfg.Options["path"] = containingId
reader, err := NewDashboardFileReader(cfg, logger)
So(err, ShouldBeNil)
err = reader.startWalkingDisk()
So(err, ShouldBeNil)
So(len(fakeService.inserted), ShouldEqual, 1)
})
Convey("Invalid configuration should return error", func() {
cfg := &DashboardsAsConfig{
Name: "Default",
@@ -0,0 +1,68 @@
{
"title": "Grafana1",
"tags": [],
"id": 3,
"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"
}
]
}
],
"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
}
+19 -6
View File
@@ -223,7 +223,7 @@ func shouldRedactURLKey(s string) bool {
return strings.Contains(uppercased, "DATABASE_URL")
}
func applyEnvVariableOverrides() {
func applyEnvVariableOverrides() error {
appliedEnvOverrides = make([]string, 0)
for _, section := range Cfg.Sections() {
for _, key := range section.Keys() {
@@ -238,7 +238,10 @@ func applyEnvVariableOverrides() {
envValue = "*********"
}
if shouldRedactURLKey(envKey) {
u, _ := url.Parse(envValue)
u, err := url.Parse(envValue)
if err != nil {
return fmt.Errorf("could not parse environment variable. key: %s, value: %s. error: %v", envKey, envValue, err)
}
ui := u.User
if ui != nil {
_, exists := ui.Password()
@@ -252,6 +255,8 @@ func applyEnvVariableOverrides() {
}
}
}
return nil
}
func applyCommandLineDefaultProperties(props map[string]string) {
@@ -377,7 +382,7 @@ func loadSpecifedConfigFile(configFile string) error {
return nil
}
func loadConfiguration(args *CommandLineArgs) {
func loadConfiguration(args *CommandLineArgs) error {
var err error
// load config defaults
@@ -395,7 +400,7 @@ func loadConfiguration(args *CommandLineArgs) {
if err != nil {
fmt.Println(fmt.Sprintf("Failed to parse defaults.ini, %v", err))
os.Exit(1)
return
return err
}
Cfg.BlockMode = false
@@ -413,7 +418,10 @@ func loadConfiguration(args *CommandLineArgs) {
}
// apply environment overrides
applyEnvVariableOverrides()
err = applyEnvVariableOverrides()
if err != nil {
return err
}
// apply command line overrides
applyCommandLineProperties(commandLineProps)
@@ -424,6 +432,8 @@ func loadConfiguration(args *CommandLineArgs) {
// update data path and logging config
DataPath = makeAbsolute(Cfg.Section("paths").Key("data").String(), HomePath)
initLogging()
return err
}
func pathExists(path string) bool {
@@ -471,7 +481,10 @@ func validateStaticRootPath() error {
func NewConfigContext(args *CommandLineArgs) error {
setHomePath(args)
loadConfiguration(args)
err := loadConfiguration(args)
if err != nil {
return err
}
Env = Cfg.Section("").Key("app_mode").MustString("development")
InstanceName = Cfg.Section("").Key("instance_name").MustString("unknown_instance_name")
+7
View File
@@ -37,6 +37,13 @@ func TestLoadingSettings(t *testing.T) {
So(appliedEnvOverrides, ShouldContain, "GF_SECURITY_ADMIN_PASSWORD=*********")
})
Convey("Should return an error when url is invalid", func() {
os.Setenv("GF_DATABASE_URL", "postgres.%31://grafana:secret@postgres:5432/grafana")
err := NewConfigContext(&CommandLineArgs{HomePath: "../../"})
So(err, ShouldNotBeNil)
})
Convey("Should replace password in URL when url environment is defined", func() {
os.Setenv("GF_DATABASE_URL", "mysql://user:secret@localhost:3306/database")
NewConfigContext(&CommandLineArgs{HomePath: "../../"})