Merge branch 'master' into teams-page-replace-mobx

This commit is contained in:
Torkel Ödegaard
2018-09-10 19:31:19 +02:00
498 changed files with 9453 additions and 6413 deletions
+4 -11
View File
@@ -192,14 +192,7 @@ func GetAlertNotifications(c *m.ReqContext) Response {
result := make([]*dtos.AlertNotification, 0)
for _, notification := range query.Result {
result = append(result, &dtos.AlertNotification{
Id: notification.Id,
Name: notification.Name,
Type: notification.Type,
IsDefault: notification.IsDefault,
Created: notification.Created,
Updated: notification.Updated,
})
result = append(result, dtos.NewAlertNotification(notification))
}
return JSON(200, result)
@@ -215,7 +208,7 @@ func GetAlertNotificationByID(c *m.ReqContext) Response {
return Error(500, "Failed to get alert notifications", err)
}
return JSON(200, query.Result)
return JSON(200, dtos.NewAlertNotification(query.Result))
}
func CreateAlertNotification(c *m.ReqContext, cmd m.CreateAlertNotificationCommand) Response {
@@ -225,7 +218,7 @@ func CreateAlertNotification(c *m.ReqContext, cmd m.CreateAlertNotificationComma
return Error(500, "Failed to create alert notification", err)
}
return JSON(200, cmd.Result)
return JSON(200, dtos.NewAlertNotification(cmd.Result))
}
func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationCommand) Response {
@@ -235,7 +228,7 @@ func UpdateAlertNotification(c *m.ReqContext, cmd m.UpdateAlertNotificationComma
return Error(500, "Failed to update alert notification", err)
}
return JSON(200, cmd.Result)
return JSON(200, dtos.NewAlertNotification(cmd.Result))
}
func DeleteAlertNotification(c *m.ReqContext) Response {
+66 -23
View File
@@ -1,35 +1,76 @@
package dtos
import (
"fmt"
"time"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/models"
)
type AlertRule struct {
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
PanelId int64 `json:"panelId"`
Name string `json:"name"`
Message string `json:"message"`
State m.AlertStateType `json:"state"`
NewStateDate time.Time `json:"newStateDate"`
EvalDate time.Time `json:"evalDate"`
EvalData *simplejson.Json `json:"evalData"`
ExecutionError string `json:"executionError"`
Url string `json:"url"`
CanEdit bool `json:"canEdit"`
Id int64 `json:"id"`
DashboardId int64 `json:"dashboardId"`
PanelId int64 `json:"panelId"`
Name string `json:"name"`
Message string `json:"message"`
State models.AlertStateType `json:"state"`
NewStateDate time.Time `json:"newStateDate"`
EvalDate time.Time `json:"evalDate"`
EvalData *simplejson.Json `json:"evalData"`
ExecutionError string `json:"executionError"`
Url string `json:"url"`
CanEdit bool `json:"canEdit"`
}
func formatShort(interval time.Duration) string {
var result string
hours := interval / time.Hour
if hours > 0 {
result += fmt.Sprintf("%dh", hours)
}
remaining := interval - (hours * time.Hour)
mins := remaining / time.Minute
if mins > 0 {
result += fmt.Sprintf("%dm", mins)
}
remaining = remaining - (mins * time.Minute)
seconds := remaining / time.Second
if seconds > 0 {
result += fmt.Sprintf("%ds", seconds)
}
return result
}
func NewAlertNotification(notification *models.AlertNotification) *AlertNotification {
return &AlertNotification{
Id: notification.Id,
Name: notification.Name,
Type: notification.Type,
IsDefault: notification.IsDefault,
Created: notification.Created,
Updated: notification.Updated,
Frequency: formatShort(notification.Frequency),
SendReminder: notification.SendReminder,
Settings: notification.Settings,
}
}
type AlertNotification struct {
Id int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
IsDefault bool `json:"isDefault"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Id int64 `json:"id"`
Name string `json:"name"`
Type string `json:"type"`
IsDefault bool `json:"isDefault"`
SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Settings *simplejson.Json `json:"settings"`
}
type AlertTestCommand struct {
@@ -39,7 +80,7 @@ type AlertTestCommand struct {
type AlertTestResult struct {
Firing bool `json:"firing"`
State m.AlertStateType `json:"state"`
State models.AlertStateType `json:"state"`
ConditionEvals string `json:"conditionEvals"`
TimeMs string `json:"timeMs"`
Error string `json:"error,omitempty"`
@@ -59,9 +100,11 @@ type EvalMatch struct {
}
type NotificationTestCommand struct {
Name string `json:"name"`
Type string `json:"type"`
Settings *simplejson.Json `json:"settings"`
Name string `json:"name"`
Type string `json:"type"`
SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"`
Settings *simplejson.Json `json:"settings"`
}
type PauseAlertCommand struct {
+35
View File
@@ -0,0 +1,35 @@
package dtos
import (
"testing"
"time"
)
func TestFormatShort(t *testing.T) {
tcs := []struct {
interval time.Duration
expected string
}{
{interval: time.Hour, expected: "1h"},
{interval: time.Hour + time.Minute, expected: "1h1m"},
{interval: (time.Hour * 10) + time.Minute, expected: "10h1m"},
{interval: (time.Hour * 10) + (time.Minute * 10) + time.Second, expected: "10h10m1s"},
{interval: time.Minute * 10, expected: "10m"},
}
for _, tc := range tcs {
got := formatShort(tc.interval)
if got != tc.expected {
t.Errorf("expected %s got %s interval: %v", tc.expected, got, tc.interval)
}
parsed, err := time.ParseDuration(tc.expected)
if err != nil {
t.Fatalf("could not parse expected duration")
}
if parsed != tc.interval {
t.Errorf("expectes the parsed duration to equal the interval. Got %v expected: %v", parsed, tc.interval)
}
}
}
+16 -14
View File
@@ -132,20 +132,22 @@ func getFrontendSettingsMap(c *m.ReqContext) (map[string]interface{}, error) {
}
jsonObj := map[string]interface{}{
"defaultDatasource": defaultDatasource,
"datasources": datasources,
"panels": panels,
"appSubUrl": setting.AppSubUrl,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"ldapEnabled": setting.LdapEnabled,
"alertingEnabled": setting.AlertingEnabled,
"exploreEnabled": setting.ExploreEnabled,
"googleAnalyticsId": setting.GoogleAnalyticsId,
"disableLoginForm": setting.DisableLoginForm,
"externalUserMngInfo": setting.ExternalUserMngInfo,
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
"defaultDatasource": defaultDatasource,
"datasources": datasources,
"panels": panels,
"appSubUrl": setting.AppSubUrl,
"allowOrgCreate": (setting.AllowUserOrgCreate && c.IsSignedIn) || c.IsGrafanaAdmin,
"authProxyEnabled": setting.AuthProxyEnabled,
"ldapEnabled": setting.LdapEnabled,
"alertingEnabled": setting.AlertingEnabled,
"alertingErrorOrTimeout": setting.AlertingErrorOrTimeout,
"alertingNoDataOrNullValues": setting.AlertingNoDataOrNullValues,
"exploreEnabled": setting.ExploreEnabled,
"googleAnalyticsId": setting.GoogleAnalyticsId,
"disableLoginForm": setting.DisableLoginForm,
"externalUserMngInfo": setting.ExternalUserMngInfo,
"externalUserMngLinkUrl": setting.ExternalUserMngLinkUrl,
"externalUserMngLinkName": setting.ExternalUserMngLinkName,
"buildInfo": map[string]interface{}{
"version": setting.BuildVersion,
"commit": setting.BuildCommit,
+1 -1
View File
@@ -70,7 +70,7 @@ func (c *connection) readPump() {
func (c *connection) handleMessage(message []byte) {
json, err := simplejson.NewJson(message)
if err != nil {
log.Error(3, "Unreadable message on websocket channel:", err)
log.Error(3, "Unreadable message on websocket channel. error: %v", err)
}
msgType := json.Get("action").MustString()
@@ -152,7 +152,7 @@ func downloadFile(pluginName, filePath, url string) (err error) {
return err
}
r, err := zip.NewReader(bytes.NewReader(body), resp.ContentLength)
r, err := zip.NewReader(bytes.NewReader(body), int64(len(body)))
if err != nil {
return err
}
+2 -2
View File
@@ -63,7 +63,7 @@ func ListAllPlugins(repoUrl string) (m.PluginRepo, error) {
var data m.PluginRepo
err = json.Unmarshal(body, &data)
if err != nil {
logger.Info("Failed to unmarshal graphite response error: %v", err)
logger.Info("Failed to unmarshal graphite response error:", err)
return m.PluginRepo{}, err
}
@@ -140,7 +140,7 @@ func GetPlugin(pluginId, repoUrl string) (m.Plugin, error) {
var data m.Plugin
err = json.Unmarshal(body, &data)
if err != nil {
logger.Info("Failed to unmarshal graphite response error: %v", err)
logger.Info("Failed to unmarshal graphite response error:", err)
return m.Plugin{}, err
}
@@ -42,6 +42,8 @@ func returnOsDefault(currentOs string) string {
return "/usr/local/var/lib/grafana/plugins"
case "freebsd":
return "/var/db/grafana/plugins"
case "openbsd":
return "/var/grafana/plugins"
default: //"linux"
return "/var/lib/grafana/plugins"
}
+9 -5
View File
@@ -96,13 +96,17 @@ func main() {
func listenToSystemSignals(server *GrafanaServerImpl) {
signalChan := make(chan os.Signal, 1)
ignoreChan := make(chan os.Signal, 1)
sighupChan := make(chan os.Signal, 1)
signal.Notify(ignoreChan, syscall.SIGHUP)
signal.Notify(sighupChan, syscall.SIGHUP)
signal.Notify(signalChan, os.Interrupt, os.Kill, syscall.SIGTERM)
select {
case sig := <-signalChan:
server.Shutdown(fmt.Sprintf("System signal: %s", sig))
for {
select {
case _ = <-sighupChan:
log.Reload()
case sig := <-signalChan:
server.Shutdown(fmt.Sprintf("System signal: %s", sig))
}
}
}
+1 -1
View File
@@ -60,7 +60,7 @@ func (u *S3Uploader) Upload(ctx context.Context, imageDiskPath string) (string,
s3_endpoint, _ := endpoints.DefaultResolver().EndpointFor("s3", u.region)
key := u.path + util.GetRandomString(20) + ".png"
image_url := s3_endpoint.URL + "/" + u.bucket + "/" + key
log.Debug("Uploading image to s3", "url = ", image_url)
log.Debug("Uploading image to s3. url = %s", image_url)
file, err := os.Open(imageDiskPath)
if err != nil {
+17
View File
@@ -236,3 +236,20 @@ func (w *FileLogWriter) Close() {
func (w *FileLogWriter) Flush() {
w.mw.fd.Sync()
}
// Reload file logger
func (w *FileLogWriter) Reload() {
// block Logger's io.Writer
w.mw.Lock()
defer w.mw.Unlock()
// Close
fd := w.mw.fd
fd.Close()
// Open again
err := w.StartLogger()
if err != nil {
fmt.Fprintf(os.Stderr, "Reload StartLogger: %s\n", err)
}
}
+4
View File
@@ -3,3 +3,7 @@ package log
type DisposableHandler interface {
Close()
}
type ReloadableHandler interface {
Reload()
}
+10 -1
View File
@@ -21,10 +21,12 @@ import (
var Root log15.Logger
var loggersToClose []DisposableHandler
var loggersToReload []ReloadableHandler
var filters map[string]log15.Lvl
func init() {
loggersToClose = make([]DisposableHandler, 0)
loggersToReload = make([]ReloadableHandler, 0)
Root = log15.Root()
Root.SetHandler(log15.DiscardHandler())
}
@@ -103,7 +105,7 @@ func Critical(skip int, format string, v ...interface{}) {
}
func Fatal(skip int, format string, v ...interface{}) {
Root.Crit(fmt.Sprintf(format, v))
Root.Crit(fmt.Sprintf(format, v...))
Close()
os.Exit(1)
}
@@ -115,6 +117,12 @@ func Close() {
loggersToClose = make([]DisposableHandler, 0)
}
func Reload() {
for _, logger := range loggersToReload {
logger.Reload()
}
}
func GetLogLevelFor(name string) Lvl {
if level, ok := filters[name]; ok {
switch level {
@@ -230,6 +238,7 @@ func ReadLoggingConfig(modes []string, logsPath string, cfg *ini.File) {
fileHandler.Init()
loggersToClose = append(loggersToClose, fileHandler)
loggersToReload = append(loggersToReload, fileHandler)
handler = fileHandler
case "syslog":
sysLogHandler := NewSyslog(sec, format)
+12 -1
View File
@@ -2,7 +2,6 @@ package login
import (
"errors"
"github.com/grafana/grafana/pkg/bus"
m "github.com/grafana/grafana/pkg/models"
)
@@ -14,6 +13,7 @@ var (
ErrProviderDeniedRequest = errors.New("Login provider denied login request")
ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter")
ErrTooManyLoginAttempts = errors.New("Too many consecutive incorrect login attempts for user. Login for user temporarily blocked")
ErrPasswordEmpty = errors.New("No password provided.")
ErrUsersQuotaReached = errors.New("Users quota reached")
ErrGettingUserQuota = errors.New("Error getting user quota")
)
@@ -28,6 +28,10 @@ func AuthenticateUser(query *m.LoginUserQuery) error {
return err
}
if err := validatePasswordSet(query.Password); err != nil {
return err
}
err := loginUsingGrafanaDB(query)
if err == nil || (err != m.ErrUserNotFound && err != ErrInvalidCredentials) {
return err
@@ -52,3 +56,10 @@ func AuthenticateUser(query *m.LoginUserQuery) error {
return err
}
func validatePasswordSet(password string) error {
if len(password) == 0 {
return ErrPasswordEmpty
}
return nil
}
+18
View File
@@ -10,6 +10,24 @@ import (
func TestAuthenticateUser(t *testing.T) {
Convey("Authenticate user", t, func() {
authScenario("When a user authenticates without setting a password", func(sc *authScenarioContext) {
mockLoginAttemptValidation(nil, sc)
mockLoginUsingGrafanaDB(nil, sc)
mockLoginUsingLdap(false, nil, sc)
loginQuery := m.LoginUserQuery{
Username: "user",
Password: "",
}
err := AuthenticateUser(&loginQuery)
Convey("login should fail", func() {
So(sc.grafanaLoginWasCalled, ShouldBeFalse)
So(sc.ldapLoginWasCalled, ShouldBeFalse)
So(err, ShouldEqual, ErrPasswordEmpty)
})
})
authScenario("When a user authenticates having too many login attempts", func(sc *authScenarioContext) {
mockLoginAttemptValidation(ErrTooManyLoginAttempts, sc)
mockLoginUsingGrafanaDB(nil, sc)
+2 -2
View File
@@ -35,7 +35,7 @@ func UpsertUser(cmd *m.UpsertUserCommand) error {
limitReached, err := quota.QuotaReached(cmd.ReqContext, "user")
if err != nil {
log.Warn("Error getting user quota", "err", err)
log.Warn("Error getting user quota. error: %v", err)
return ErrGettingUserQuota
}
if limitReached {
@@ -135,7 +135,7 @@ func updateUser(user *m.User, extUser *m.ExternalUserInfo) error {
return nil
}
log.Debug("Syncing user info", "id", user.Id, "update", updateCmd)
log.Debug2("Syncing user info", "id", user.Id, "update", updateCmd)
return bus.Dispatch(updateCmd)
}
+10
View File
@@ -440,6 +440,16 @@ func sendUsageStats() {
metrics["stats.ds_access.other."+access+".count"] = count
}
anStats := models.GetAlertNotifierUsageStatsQuery{}
if err := bus.Dispatch(&anStats); err != nil {
metricsLogger.Error("Failed to get alert notification stats", "error", err)
return
}
for _, stats := range anStats.Result {
metrics["stats.alert_notifiers."+stats.Type+".count"] = stats.Count
}
out, _ := json.MarshalIndent(report, "", " ")
data := bytes.NewBuffer(out)
+22
View File
@@ -115,6 +115,24 @@ func TestMetrics(t *testing.T) {
return nil
})
var getAlertNotifierUsageStatsQuery *models.GetAlertNotifierUsageStatsQuery
bus.AddHandler("test", func(query *models.GetAlertNotifierUsageStatsQuery) error {
query.Result = []*models.NotifierUsageStats{
{
Type: "slack",
Count: 1,
},
{
Type: "webhook",
Count: 2,
},
}
getAlertNotifierUsageStatsQuery = query
return nil
})
var wg sync.WaitGroup
var responseBuffer *bytes.Buffer
var req *http.Request
@@ -157,6 +175,7 @@ func TestMetrics(t *testing.T) {
So(getSystemStatsQuery, ShouldNotBeNil)
So(getDataSourceStatsQuery, ShouldNotBeNil)
So(getDataSourceAccessStatsQuery, ShouldNotBeNil)
So(getAlertNotifierUsageStatsQuery, ShouldNotBeNil)
So(req, ShouldNotBeNil)
So(req.Method, ShouldEqual, http.MethodPost)
So(req.Header.Get("Content-Type"), ShouldEqual, "application/json")
@@ -198,6 +217,9 @@ func TestMetrics(t *testing.T) {
So(metrics.Get("stats.ds_access."+models.DS_PROMETHEUS+".proxy.count").MustInt(), ShouldEqual, 3)
So(metrics.Get("stats.ds_access.other.direct.count").MustInt(), ShouldEqual, 6+7)
So(metrics.Get("stats.ds_access.other.proxy.count").MustInt(), ShouldEqual, 4+8)
So(metrics.Get("stats.alert_notifiers.slack.count").MustInt(), ShouldEqual, 1)
So(metrics.Get("stats.alert_notifiers.webhook.count").MustInt(), ShouldEqual, 2)
})
})
+3 -3
View File
@@ -36,7 +36,7 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
// initialize session
if err := ctx.Session.Start(ctx.Context); err != nil {
log.Error(3, "Failed to start session", err)
log.Error(3, "Failed to start session. error %v", err)
return false
}
@@ -146,12 +146,12 @@ func initContextWithAuthProxy(ctx *m.ReqContext, orgID int64) bool {
if getRequestUserId(ctx) > 0 && getRequestUserId(ctx) != query.Result.UserId {
// remove session
if err := ctx.Session.Destory(ctx.Context); err != nil {
log.Error(3, "Failed to destroy session, err")
log.Error(3, "Failed to destroy session. error: %v", err)
}
// initialize a new session
if err := ctx.Session.Start(ctx.Context); err != nil {
log.Error(3, "Failed to start session", err)
log.Error(3, "Failed to start session. error: %v", err)
}
}
+60 -17
View File
@@ -1,38 +1,50 @@
package models
import (
"errors"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
)
var (
ErrNotificationFrequencyNotFound = errors.New("Notification frequency not specified")
ErrJournalingNotFound = errors.New("alert notification journaling not found")
)
type AlertNotification struct {
Id int64 `json:"id"`
OrgId int64 `json:"-"`
Name string `json:"name"`
Type string `json:"type"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
Id int64 `json:"id"`
OrgId int64 `json:"-"`
Name string `json:"name"`
Type string `json:"type"`
SendReminder bool `json:"sendReminder"`
Frequency time.Duration `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"`
Created time.Time `json:"created"`
Updated time.Time `json:"updated"`
}
type CreateAlertNotificationCommand struct {
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings"`
OrgId int64 `json:"-"`
Result *AlertNotification
}
type UpdateAlertNotificationCommand struct {
Id int64 `json:"id" binding:"Required"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings" binding:"Required"`
Id int64 `json:"id" binding:"Required"`
Name string `json:"name" binding:"Required"`
Type string `json:"type" binding:"Required"`
SendReminder bool `json:"sendReminder"`
Frequency string `json:"frequency"`
IsDefault bool `json:"isDefault"`
Settings *simplejson.Json `json:"settings" binding:"Required"`
OrgId int64 `json:"-"`
Result *AlertNotification
@@ -63,3 +75,34 @@ type GetAllAlertNotificationsQuery struct {
Result []*AlertNotification
}
type AlertNotificationJournal struct {
Id int64
OrgId int64
AlertId int64
NotifierId int64
SentAt int64
Success bool
}
type RecordNotificationJournalCommand struct {
OrgId int64
AlertId int64
NotifierId int64
SentAt int64
Success bool
}
type GetLatestNotificationQuery struct {
OrgId int64
AlertId int64
NotifierId int64
Result *AlertNotificationJournal
}
type CleanNotificationJournalCommand struct {
OrgId int64
AlertId int64
NotifierId int64
}
+9
View File
@@ -40,6 +40,15 @@ type GetDataSourceAccessStatsQuery struct {
Result []*DataSourceAccessStats
}
type NotifierUsageStats struct {
Type string
Count int64
}
type GetAlertNotifierUsageStatsQuery struct {
Result []*NotifierUsageStats
}
type AdminStats struct {
Users int `json:"users"`
Orgs int `json:"orgs"`
+9 -2
View File
@@ -1,6 +1,9 @@
package alerting
import "time"
import (
"context"
"time"
)
type EvalHandler interface {
Eval(evalContext *EvalContext)
@@ -15,10 +18,14 @@ type Notifier interface {
Notify(evalContext *EvalContext) error
GetType() string
NeedsImage() bool
ShouldNotify(evalContext *EvalContext) bool
// ShouldNotify checks this evaluation should send an alert notification
ShouldNotify(ctx context.Context, evalContext *EvalContext) bool
GetNotifierId() int64
GetIsDefault() bool
GetSendReminder() bool
GetFrequency() time.Duration
}
type NotifierSlice []Notifier
+43 -12
View File
@@ -1,10 +1,10 @@
package alerting
import (
"context"
"errors"
"fmt"
"golang.org/x/sync/errgroup"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/imguploader"
@@ -58,17 +58,47 @@ func (n *notificationService) SendIfNeeded(context *EvalContext) error {
return n.sendNotifications(context, notifiers)
}
func (n *notificationService) sendNotifications(context *EvalContext, notifiers []Notifier) error {
g, _ := errgroup.WithContext(context.Ctx)
func (n *notificationService) sendNotifications(evalContext *EvalContext, notifiers []Notifier) error {
for _, notifier := range notifiers {
not := notifier //avoid updating scope variable in go routine
n.log.Debug("Sending notification", "type", not.GetType(), "id", not.GetNotifierId(), "isDefault", not.GetIsDefault())
metrics.M_Alerting_Notification_Sent.WithLabelValues(not.GetType()).Inc()
g.Go(func() error { return not.Notify(context) })
not := notifier
err := bus.InTransaction(evalContext.Ctx, func(ctx context.Context) error {
n.log.Debug("trying to send notification", "id", not.GetNotifierId())
// Verify that we can send the notification again
// but this time within the same transaction.
if !evalContext.IsTestRun && !not.ShouldNotify(context.Background(), evalContext) {
return nil
}
n.log.Debug("Sending notification", "type", not.GetType(), "id", not.GetNotifierId(), "isDefault", not.GetIsDefault())
metrics.M_Alerting_Notification_Sent.WithLabelValues(not.GetType()).Inc()
//send notification
success := not.Notify(evalContext) == nil
if evalContext.IsTestRun {
return nil
}
//write result to db.
cmd := &m.RecordNotificationJournalCommand{
OrgId: evalContext.Rule.OrgId,
AlertId: evalContext.Rule.Id,
NotifierId: not.GetNotifierId(),
SentAt: time.Now().Unix(),
Success: success,
}
return bus.DispatchCtx(ctx, cmd)
})
if err != nil {
n.log.Error("failed to send notification", "id", not.GetNotifierId())
}
}
return g.Wait()
return nil
}
func (n *notificationService) uploadImage(context *EvalContext) (err error) {
@@ -110,7 +140,7 @@ func (n *notificationService) uploadImage(context *EvalContext) (err error) {
return nil
}
func (n *notificationService) getNeededNotifiers(orgId int64, notificationIds []int64, context *EvalContext) (NotifierSlice, error) {
func (n *notificationService) getNeededNotifiers(orgId int64, notificationIds []int64, evalContext *EvalContext) (NotifierSlice, error) {
query := &m.GetAlertNotificationsToSendQuery{OrgId: orgId, Ids: notificationIds}
if err := bus.Dispatch(query); err != nil {
@@ -123,7 +153,8 @@ func (n *notificationService) getNeededNotifiers(orgId int64, notificationIds []
if err != nil {
return nil, err
}
if not.ShouldNotify(context) {
if not.ShouldNotify(evalContext.Ctx, evalContext) {
result = append(result, not)
}
}
@@ -1,6 +1,7 @@
package notifiers
import (
"context"
"time"
"github.com/grafana/grafana/pkg/bus"
@@ -33,7 +34,7 @@ func NewAlertmanagerNotifier(model *m.AlertNotification) (alerting.Notifier, err
}
return &AlertmanagerNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Url: url,
log: log.New("alerting.notifier.prometheus-alertmanager"),
}, nil
@@ -45,7 +46,7 @@ type AlertmanagerNotifier struct {
log log.Logger
}
func (this *AlertmanagerNotifier) ShouldNotify(evalContext *alerting.EvalContext) bool {
func (this *AlertmanagerNotifier) ShouldNotify(ctx context.Context, evalContext *alerting.EvalContext) bool {
this.log.Debug("Should notify", "ruleId", evalContext.Rule.Id, "state", evalContext.Rule.State, "previousState", evalContext.PrevAlertState)
// Do not notify when we become OK for the first time.
+71 -19
View File
@@ -1,50 +1,94 @@
package notifiers
import (
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
"context"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/services/alerting"
)
type NotifierBase struct {
Name string
Type string
Id int64
IsDeault bool
UploadImage bool
Name string
Type string
Id int64
IsDeault bool
UploadImage bool
SendReminder bool
Frequency time.Duration
log log.Logger
}
func NewNotifierBase(id int64, isDefault bool, name, notifierType string, model *simplejson.Json) NotifierBase {
func NewNotifierBase(model *models.AlertNotification) NotifierBase {
uploadImage := true
value, exist := model.CheckGet("uploadImage")
value, exist := model.Settings.CheckGet("uploadImage")
if exist {
uploadImage = value.MustBool()
}
return NotifierBase{
Id: id,
Name: name,
IsDeault: isDefault,
Type: notifierType,
UploadImage: uploadImage,
Id: model.Id,
Name: model.Name,
IsDeault: model.IsDefault,
Type: model.Type,
UploadImage: uploadImage,
SendReminder: model.SendReminder,
Frequency: model.Frequency,
log: log.New("alerting.notifier." + model.Name),
}
}
func defaultShouldNotify(context *alerting.EvalContext) bool {
func defaultShouldNotify(context *alerting.EvalContext, sendReminder bool, frequency time.Duration, lastNotify time.Time) bool {
// Only notify on state change.
if context.PrevAlertState == context.Rule.State {
if context.PrevAlertState == context.Rule.State && !sendReminder {
return false
}
// Do not notify if interval has not elapsed
if sendReminder && !lastNotify.IsZero() && lastNotify.Add(frequency).After(time.Now()) {
return false
}
// Do not notify if alert state if OK or pending even on repeated notify
if sendReminder && (context.Rule.State == models.AlertStateOK || context.Rule.State == models.AlertStatePending) {
return false
}
// Do not notify when we become OK for the first time.
if (context.PrevAlertState == m.AlertStatePending) && (context.Rule.State == m.AlertStateOK) {
if (context.PrevAlertState == models.AlertStatePending) && (context.Rule.State == models.AlertStateOK) {
return false
}
return true
}
func (n *NotifierBase) ShouldNotify(context *alerting.EvalContext) bool {
return defaultShouldNotify(context)
// ShouldNotify checks this evaluation should send an alert notification
func (n *NotifierBase) ShouldNotify(ctx context.Context, c *alerting.EvalContext) bool {
cmd := &models.GetLatestNotificationQuery{
OrgId: c.Rule.OrgId,
AlertId: c.Rule.Id,
NotifierId: n.Id,
}
err := bus.DispatchCtx(ctx, cmd)
if err == models.ErrJournalingNotFound {
return true
}
if err != nil {
n.log.Error("Could not determine last time alert notifier fired", "Alert name", c.Rule.Name, "Error", err)
return false
}
if !cmd.Result.Success {
return true
}
return defaultShouldNotify(c, n.SendReminder, n.Frequency, time.Unix(cmd.Result.SentAt, 0))
}
func (n *NotifierBase) GetType() string {
@@ -62,3 +106,11 @@ func (n *NotifierBase) GetNotifierId() int64 {
func (n *NotifierBase) GetIsDefault() bool {
return n.IsDeault
}
func (n *NotifierBase) GetSendReminder() bool {
return n.SendReminder
}
func (n *NotifierBase) GetFrequency() time.Duration {
return n.Frequency
}
+121 -35
View File
@@ -2,7 +2,11 @@ package notifiers
import (
"context"
"errors"
"testing"
"time"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
@@ -10,47 +14,129 @@ import (
. "github.com/smartystreets/goconvey/convey"
)
func TestBaseNotifier(t *testing.T) {
Convey("Base notifier tests", t, func() {
Convey("default constructor for notifiers", func() {
bJson := simplejson.New()
func TestShouldSendAlertNotification(t *testing.T) {
tcs := []struct {
name string
prevState m.AlertStateType
newState m.AlertStateType
expected bool
sendReminder bool
}{
{
name: "pending -> ok should not trigger an notification",
newState: m.AlertStatePending,
prevState: m.AlertStateOK,
expected: false,
},
{
name: "ok -> alerting should trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateAlerting,
expected: true,
},
{
name: "ok -> pending should not trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStatePending,
expected: false,
},
{
name: "ok -> ok should not trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateOK,
expected: false,
sendReminder: false,
},
{
name: "ok -> alerting should not trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateAlerting,
expected: true,
sendReminder: true,
},
{
name: "ok -> ok with reminder should not trigger an notification",
newState: m.AlertStateOK,
prevState: m.AlertStateOK,
expected: false,
sendReminder: true,
},
}
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)
})
for _, tc := range tcs {
evalContext := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
State: tc.newState,
})
Convey("should notify", func() {
Convey("pending -> ok", func() {
context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
State: m.AlertStatePending,
})
context.Rule.State = m.AlertStateOK
So(defaultShouldNotify(context), ShouldBeFalse)
evalContext.Rule.State = tc.prevState
if defaultShouldNotify(evalContext, true, 0, time.Now()) != tc.expected {
t.Errorf("failed %s. expected %+v to return %v", tc.name, tc, tc.expected)
}
}
}
func TestShouldNotifyWhenNoJournalingIsFound(t *testing.T) {
Convey("base notifier", t, func() {
bus.ClearBusHandlers()
notifier := NewNotifierBase(&m.AlertNotification{
Id: 1,
Name: "name",
Type: "email",
Settings: simplejson.New(),
})
evalContext := alerting.NewEvalContext(context.TODO(), &alerting.Rule{})
Convey("should notify if no journaling is found", func() {
bus.AddHandlerCtx("", func(ctx context.Context, q *m.GetLatestNotificationQuery) error {
return m.ErrJournalingNotFound
})
Convey("ok -> alerting", func() {
context := alerting.NewEvalContext(context.TODO(), &alerting.Rule{
State: m.AlertStateOK,
})
context.Rule.State = m.AlertStateAlerting
So(defaultShouldNotify(context), ShouldBeTrue)
if !notifier.ShouldNotify(context.Background(), evalContext) {
t.Errorf("should send notifications when ErrJournalingNotFound is returned")
}
})
Convey("should not notify query returns error", func() {
bus.AddHandlerCtx("", func(ctx context.Context, q *m.GetLatestNotificationQuery) error {
return errors.New("some kind of error unknown error")
})
if notifier.ShouldNotify(context.Background(), evalContext) {
t.Errorf("should not send notifications when query returns error")
}
})
})
}
func TestBaseNotifier(t *testing.T) {
Convey("default constructor for notifiers", t, func() {
bJson := simplejson.New()
model := &m.AlertNotification{
Id: 1,
Name: "name",
Type: "email",
Settings: bJson,
}
Convey("can parse false value", func() {
bJson.Set("uploadImage", false)
base := NewNotifierBase(model)
So(base.UploadImage, ShouldBeFalse)
})
Convey("can parse true value", func() {
bJson.Set("uploadImage", true)
base := NewNotifierBase(model)
So(base.UploadImage, ShouldBeTrue)
})
Convey("default value should be true for backwards compatibility", func() {
base := NewNotifierBase(model)
So(base.UploadImage, ShouldBeTrue)
})
})
}
+1 -1
View File
@@ -32,7 +32,7 @@ func NewDingDingNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}
return &DingDingNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Url: url,
log: log.New("alerting.notifier.dingding"),
}, nil
+1 -1
View File
@@ -39,7 +39,7 @@ func NewDiscordNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}
return &DiscordNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
WebhookURL: url,
log: log.New("alerting.notifier.discord"),
}, nil
+1 -1
View File
@@ -52,7 +52,7 @@ func NewEmailNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
})
return &EmailNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Addresses: addresses,
log: log.New("alerting.notifier.email"),
}, nil
+1 -1
View File
@@ -59,7 +59,7 @@ func NewHipChatNotifier(model *models.AlertNotification) (alerting.Notifier, err
roomId := model.Settings.Get("roomid").MustString()
return &HipChatNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Url: url,
ApiKey: apikey,
RoomId: roomId,
+1 -1
View File
@@ -43,7 +43,7 @@ func NewKafkaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}
return &KafkaNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Endpoint: endpoint,
Topic: topic,
log: log.New("alerting.notifier.kafka"),
+1 -1
View File
@@ -39,7 +39,7 @@ func NewLINENotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}
return &LineNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Token: token,
log: log.New("alerting.notifier.line"),
}, nil
+1 -1
View File
@@ -56,7 +56,7 @@ func NewOpsGenieNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}
return &OpsGenieNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
ApiKey: apiKey,
ApiUrl: apiUrl,
AutoClose: autoClose,
+1 -1
View File
@@ -51,7 +51,7 @@ func NewPagerdutyNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}
return &PagerdutyNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Key: key,
AutoResolve: autoResolve,
log: log.New("alerting.notifier.pagerduty"),
+1 -1
View File
@@ -99,7 +99,7 @@ func NewPushoverNotifier(model *m.AlertNotification) (alerting.Notifier, error)
return nil, alerting.ValidationError{Reason: "API token not given"}
}
return &PushoverNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
UserKey: userKey,
ApiToken: apiToken,
Priority: priority,
+1 -1
View File
@@ -51,7 +51,7 @@ func NewSensuNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}
return &SensuNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Url: url,
User: model.Settings.Get("username").MustString(),
Source: model.Settings.Get("source").MustString(),
+1 -1
View File
@@ -78,7 +78,7 @@ func NewSlackNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
uploadImage := model.Settings.Get("uploadImage").MustBool(true)
return &SlackNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Url: url,
Recipient: recipient,
Mention: mention,
+20 -8
View File
@@ -33,7 +33,7 @@ func NewTeamsNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}
return &TeamsNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Url: url,
log: log.New("alerting.notifier.teams"),
}, nil
@@ -96,14 +96,26 @@ func (this *TeamsNotifier) Notify(evalContext *alerting.EvalContext) error {
},
},
"text": message,
"potentialAction": []map[string]interface{}{
},
},
"potentialAction": []map[string]interface{}{
{
"@context": "http://schema.org",
"@type": "OpenUri",
"name": "View Rule",
"targets": []map[string]interface{}{
{
"@context": "http://schema.org",
"@type": "ViewAction",
"name": "View Rule",
"target": []string{
ruleUrl,
},
"os": "default", "uri": ruleUrl,
},
},
},
{
"@context": "http://schema.org",
"@type": "OpenUri",
"name": "View Graph",
"targets": []map[string]interface{}{
{
"os": "default", "uri": evalContext.ImagePublicUrl,
},
},
},
+2 -2
View File
@@ -78,7 +78,7 @@ func NewTelegramNotifier(model *m.AlertNotification) (alerting.Notifier, error)
}
return &TelegramNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
BotToken: botToken,
ChatID: chatId,
UploadImage: uploadImage,
@@ -216,7 +216,7 @@ func appendIfPossible(message string, extra string, sizeLimit int) string {
if len(extra)+len(message) <= sizeLimit {
return message + extra
}
log.Debug("Line too long for image caption.", "value", extra)
log.Debug("Line too long for image caption. value: %s", extra)
return message
}
+1 -1
View File
@@ -106,7 +106,7 @@ func NewThreemaNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}
return &ThreemaNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
GatewayID: gatewayID,
RecipientID: recipientID,
APISecret: apiSecret,
+1 -1
View File
@@ -51,7 +51,7 @@ func NewVictoropsNotifier(model *models.AlertNotification) (alerting.Notifier, e
}
return &VictoropsNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
URL: url,
AutoResolve: autoResolve,
log: log.New("alerting.notifier.victorops"),
+1 -1
View File
@@ -47,7 +47,7 @@ func NewWebHookNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
}
return &WebhookNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
NotifierBase: NewNotifierBase(model),
Url: url,
User: model.Settings.Get("username").MustString(),
Password: model.Settings.Get("password").MustString(),
+12
View File
@@ -88,6 +88,18 @@ func (handler *DefaultResultHandler) Handle(evalContext *EvalContext) error {
}
}
if evalContext.Rule.State == m.AlertStateOK && evalContext.PrevAlertState != m.AlertStateOK {
for _, notifierId := range evalContext.Rule.Notifications {
cmd := &m.CleanNotificationJournalCommand{
AlertId: evalContext.Rule.Id,
NotifierId: notifierId,
OrgId: evalContext.Rule.OrgId,
}
if err := bus.DispatchCtx(evalContext.Ctx, cmd); err != nil {
handler.log.Error("Failed to clean up old notification records", "notifier", notifierId, "alert", evalContext.Rule.Id, "Error", err)
}
}
}
handler.notifier.SendIfNeeded(evalContext)
return nil
+37 -9
View File
@@ -2,6 +2,7 @@ package rendering
import (
"context"
"fmt"
"io"
"net"
"net/http"
@@ -20,14 +21,13 @@ var netTransport = &http.Transport{
TLSHandshakeTimeout: 5 * time.Second,
}
var netClient = &http.Client{
Transport: netTransport,
}
func (rs *RenderingService) renderViaHttp(ctx context.Context, opts Opts) (*RenderResult, error) {
filePath := rs.getFilePathForNewImage()
var netClient = &http.Client{
Timeout: opts.Timeout,
Transport: netTransport,
}
rendererUrl, err := url.Parse(rs.Cfg.RendererUrl)
if err != nil {
return nil, err
@@ -35,10 +35,10 @@ func (rs *RenderingService) renderViaHttp(ctx context.Context, opts Opts) (*Rend
queryParams := rendererUrl.Query()
queryParams.Add("url", rs.getURL(opts.Path))
queryParams.Add("renderKey", rs.getRenderKey(opts.UserId, opts.OrgId, opts.OrgRole))
queryParams.Add("renderKey", rs.getRenderKey(opts.OrgId, opts.UserId, opts.OrgRole))
queryParams.Add("width", strconv.Itoa(opts.Width))
queryParams.Add("height", strconv.Itoa(opts.Height))
queryParams.Add("domain", rs.getLocalDomain())
queryParams.Add("domain", rs.domain)
queryParams.Add("timezone", isoTimeOffsetToPosixTz(opts.Timezone))
queryParams.Add("encoding", opts.Encoding)
queryParams.Add("timeout", strconv.Itoa(int(opts.Timeout.Seconds())))
@@ -49,20 +49,48 @@ func (rs *RenderingService) renderViaHttp(ctx context.Context, opts Opts) (*Rend
return nil, err
}
reqContext, cancel := context.WithTimeout(ctx, opts.Timeout+time.Second*2)
defer cancel()
req = req.WithContext(reqContext)
// make request to renderer server
resp, err := netClient.Do(req)
if err != nil {
return nil, err
rs.log.Error("Failed to send request to remote rendering service.", "error", err)
return nil, fmt.Errorf("Failed to send request to remote rendering service. %s", err)
}
// save response to file
defer resp.Body.Close()
// check for timeout first
if reqContext.Err() == context.DeadlineExceeded {
rs.log.Info("Rendering timed out")
return nil, ErrTimeout
}
// if we didnt get a 200 response, something went wrong.
if resp.StatusCode != http.StatusOK {
rs.log.Error("Remote rendering request failed", "error", resp.Status)
return nil, fmt.Errorf("Remote rendering request failed. %d: %s", resp.StatusCode, resp.Status)
}
out, err := os.Create(filePath)
if err != nil {
return nil, err
}
defer out.Close()
io.Copy(out, resp.Body)
_, err = io.Copy(out, resp.Body)
if err != nil {
// check that we didnt timeout while receiving the response.
if reqContext.Err() == context.DeadlineExceeded {
rs.log.Info("Rendering timed out")
return nil, ErrTimeout
}
rs.log.Error("Remote rendering request failed", "error", err)
return nil, fmt.Errorf("Remote rendering request failed. %s", err)
}
return &RenderResult{FilePath: filePath}, err
}
+1 -1
View File
@@ -49,7 +49,7 @@ func (rs *RenderingService) renderViaPhantomJS(ctx context.Context, opts Opts) (
fmt.Sprintf("width=%v", opts.Width),
fmt.Sprintf("height=%v", opts.Height),
fmt.Sprintf("png=%v", pngPath),
fmt.Sprintf("domain=%v", rs.getLocalDomain()),
fmt.Sprintf("domain=%v", rs.domain),
fmt.Sprintf("timeout=%v", opts.Timeout.Seconds()),
fmt.Sprintf("renderKey=%v", renderKey),
}
+2 -2
View File
@@ -77,10 +77,10 @@ func (rs *RenderingService) renderViaPlugin(ctx context.Context, opts Opts) (*Re
Height: int32(opts.Height),
FilePath: pngPath,
Timeout: int32(opts.Timeout.Seconds()),
RenderKey: rs.getRenderKey(opts.UserId, opts.OrgId, opts.OrgRole),
RenderKey: rs.getRenderKey(opts.OrgId, opts.UserId, opts.OrgRole),
Encoding: opts.Encoding,
Timezone: isoTimeOffsetToPosixTz(opts.Timezone),
Domain: rs.getLocalDomain(),
Domain: rs.domain,
})
if err != nil {
+30 -8
View File
@@ -3,6 +3,8 @@ package rendering
import (
"context"
"fmt"
"net/url"
"os"
"path/filepath"
plugin "github.com/hashicorp/go-plugin"
@@ -27,12 +29,31 @@ type RenderingService struct {
grpcPlugin pluginModel.RendererPlugin
pluginInfo *plugins.RendererPlugin
renderAction renderFunc
domain string
Cfg *setting.Cfg `inject:""`
}
func (rs *RenderingService) Init() error {
rs.log = log.New("rendering")
// ensure ImagesDir exists
err := os.MkdirAll(rs.Cfg.ImagesDir, 0700)
if err != nil {
return err
}
// set value used for domain attribute of renderKey cookie
if rs.Cfg.RendererUrl != "" {
// RendererCallbackUrl has already been passed, it wont generate an error.
u, _ := url.Parse(rs.Cfg.RendererCallbackUrl)
rs.domain = u.Hostname()
} else if setting.HttpAddr != setting.DEFAULT_HTTP_ADDR {
rs.domain = setting.HttpAddr
} else {
rs.domain = "localhost"
}
return nil
}
@@ -82,16 +103,17 @@ func (rs *RenderingService) getFilePathForNewImage() string {
}
func (rs *RenderingService) getURL(path string) string {
// &render=1 signals to the legacy redirect layer to
return fmt.Sprintf("%s://%s:%s/%s&render=1", setting.Protocol, rs.getLocalDomain(), setting.HttpPort, path)
}
if rs.Cfg.RendererUrl != "" {
// The backend rendering service can potentially be remote.
// So we need to use the root_url to ensure the rendering service
// can reach this Grafana instance.
// &render=1 signals to the legacy redirect layer to
return fmt.Sprintf("%s%s&render=1", rs.Cfg.RendererCallbackUrl, path)
func (rs *RenderingService) getLocalDomain() string {
if setting.HttpAddr != setting.DEFAULT_HTTP_ADDR {
return setting.HttpAddr
}
return "localhost"
// &render=1 signals to the legacy redirect layer to
return fmt.Sprintf("%s://%s:%s/%s&render=1", setting.Protocol, rs.domain, setting.HttpPort, path)
}
func (rs *RenderingService) getRenderKey(orgId, userId int64, orgRole models.RoleType) string {
+96 -13
View File
@@ -2,6 +2,7 @@ package sqlstore
import (
"bytes"
"context"
"fmt"
"strings"
"time"
@@ -17,6 +18,9 @@ func init() {
bus.AddHandler("sql", DeleteAlertNotification)
bus.AddHandler("sql", GetAlertNotificationsToSend)
bus.AddHandler("sql", GetAllAlertNotifications)
bus.AddHandlerCtx("sql", RecordNotificationJournal)
bus.AddHandlerCtx("sql", GetLatestNotification)
bus.AddHandlerCtx("sql", CleanNotificationJournal)
}
func DeleteAlertNotification(cmd *m.DeleteAlertNotificationCommand) error {
@@ -53,7 +57,9 @@ func GetAlertNotificationsToSend(query *m.GetAlertNotificationsToSendQuery) erro
alert_notification.created,
alert_notification.updated,
alert_notification.settings,
alert_notification.is_default
alert_notification.is_default,
alert_notification.send_reminder,
alert_notification.frequency
FROM alert_notification
`)
@@ -91,7 +97,9 @@ func getAlertNotificationInternal(query *m.GetAlertNotificationsQuery, sess *DBS
alert_notification.created,
alert_notification.updated,
alert_notification.settings,
alert_notification.is_default
alert_notification.is_default,
alert_notification.send_reminder,
alert_notification.frequency
FROM alert_notification
`)
@@ -137,17 +145,31 @@ func CreateAlertNotificationCommand(cmd *m.CreateAlertNotificationCommand) error
return fmt.Errorf("Alert notification name %s already exists", cmd.Name)
}
alertNotification := &m.AlertNotification{
OrgId: cmd.OrgId,
Name: cmd.Name,
Type: cmd.Type,
Settings: cmd.Settings,
Created: time.Now(),
Updated: time.Now(),
IsDefault: cmd.IsDefault,
var frequency time.Duration
if cmd.SendReminder {
if cmd.Frequency == "" {
return m.ErrNotificationFrequencyNotFound
}
frequency, err = time.ParseDuration(cmd.Frequency)
if err != nil {
return err
}
}
if _, err = sess.Insert(alertNotification); err != nil {
alertNotification := &m.AlertNotification{
OrgId: cmd.OrgId,
Name: cmd.Name,
Type: cmd.Type,
Settings: cmd.Settings,
SendReminder: cmd.SendReminder,
Frequency: frequency,
Created: time.Now(),
Updated: time.Now(),
IsDefault: cmd.IsDefault,
}
if _, err = sess.MustCols("send_reminder").Insert(alertNotification); err != nil {
return err
}
@@ -179,16 +201,77 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
current.Name = cmd.Name
current.Type = cmd.Type
current.IsDefault = cmd.IsDefault
current.SendReminder = cmd.SendReminder
sess.UseBool("is_default")
if current.SendReminder {
if cmd.Frequency == "" {
return m.ErrNotificationFrequencyNotFound
}
frequency, err := time.ParseDuration(cmd.Frequency)
if err != nil {
return err
}
current.Frequency = frequency
}
sess.UseBool("is_default", "send_reminder")
if affected, err := sess.ID(cmd.Id).Update(current); err != nil {
return err
} else if affected == 0 {
return fmt.Errorf("Could not find alert notification")
return fmt.Errorf("Could not update alert notification")
}
cmd.Result = &current
return nil
})
}
func RecordNotificationJournal(ctx context.Context, cmd *m.RecordNotificationJournalCommand) error {
return inTransactionCtx(ctx, func(sess *DBSession) error {
journalEntry := &m.AlertNotificationJournal{
OrgId: cmd.OrgId,
AlertId: cmd.AlertId,
NotifierId: cmd.NotifierId,
SentAt: cmd.SentAt,
Success: cmd.Success,
}
if _, err := sess.Insert(journalEntry); err != nil {
return err
}
return nil
})
}
func GetLatestNotification(ctx context.Context, cmd *m.GetLatestNotificationQuery) error {
return inTransactionCtx(ctx, func(sess *DBSession) error {
nj := &m.AlertNotificationJournal{}
_, err := sess.Desc("alert_notification_journal.sent_at").
Limit(1).
Where("alert_notification_journal.org_id = ? AND alert_notification_journal.alert_id = ? AND alert_notification_journal.notifier_id = ?", cmd.OrgId, cmd.AlertId, cmd.NotifierId).Get(nj)
if err != nil {
return err
}
if nj.AlertId == 0 && nj.Id == 0 && nj.NotifierId == 0 && nj.OrgId == 0 {
return m.ErrJournalingNotFound
}
cmd.Result = nj
return nil
})
}
func CleanNotificationJournal(ctx context.Context, cmd *m.CleanNotificationJournalCommand) error {
return inTransactionCtx(ctx, func(sess *DBSession) error {
sql := "DELETE FROM alert_notification_journal WHERE alert_notification_journal.org_id = ? AND alert_notification_journal.alert_id = ? AND alert_notification_journal.notifier_id = ?"
_, err := sess.Exec(sql, cmd.OrgId, cmd.AlertId, cmd.NotifierId)
return err
})
}
+133 -17
View File
@@ -1,7 +1,9 @@
package sqlstore
import (
"context"
"testing"
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
m "github.com/grafana/grafana/pkg/models"
@@ -11,7 +13,48 @@ import (
func TestAlertNotificationSQLAccess(t *testing.T) {
Convey("Testing Alert notification sql access", t, func() {
InitTestDB(t)
var err error
Convey("Alert notification journal", func() {
var alertId int64 = 5
var orgId int64 = 5
var notifierId int64 = 5
Convey("Getting last journal should raise error if no one exists", func() {
query := &m.GetLatestNotificationQuery{AlertId: alertId, OrgId: orgId, NotifierId: notifierId}
err := GetLatestNotification(context.Background(), query)
So(err, ShouldEqual, m.ErrJournalingNotFound)
Convey("shoulbe be able to record two journaling events", func() {
createCmd := &m.RecordNotificationJournalCommand{AlertId: alertId, NotifierId: notifierId, OrgId: orgId, Success: true, SentAt: 1}
err := RecordNotificationJournal(context.Background(), createCmd)
So(err, ShouldBeNil)
createCmd.SentAt += 1000 //increase epoch
err = RecordNotificationJournal(context.Background(), createCmd)
So(err, ShouldBeNil)
Convey("get last journaling event", func() {
err := GetLatestNotification(context.Background(), query)
So(err, ShouldBeNil)
So(query.Result.SentAt, ShouldEqual, 1001)
Convey("be able to clear all journaling for an notifier", func() {
cmd := &m.CleanNotificationJournalCommand{AlertId: alertId, NotifierId: notifierId, OrgId: orgId}
err := CleanNotificationJournal(context.Background(), cmd)
So(err, ShouldBeNil)
Convey("querying for last junaling should raise error", func() {
query := &m.GetLatestNotificationQuery{AlertId: alertId, OrgId: orgId, NotifierId: notifierId}
err := GetLatestNotification(context.Background(), query)
So(err, ShouldEqual, m.ErrJournalingNotFound)
})
})
})
})
})
})
Convey("Alert notifications should be empty", func() {
cmd := &m.GetAlertNotificationsQuery{
@@ -24,19 +67,75 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
So(cmd.Result, ShouldBeNil)
})
Convey("Can save Alert Notification", func() {
Convey("Cannot save alert notifier with send reminder = true", func() {
cmd := &m.CreateAlertNotificationCommand{
Name: "ops",
Type: "email",
OrgId: 1,
Settings: simplejson.New(),
Name: "ops",
Type: "email",
OrgId: 1,
SendReminder: true,
Settings: simplejson.New(),
}
err = CreateAlertNotificationCommand(cmd)
Convey("and missing frequency", func() {
err := CreateAlertNotificationCommand(cmd)
So(err, ShouldEqual, m.ErrNotificationFrequencyNotFound)
})
Convey("invalid frequency", func() {
cmd.Frequency = "invalid duration"
err := CreateAlertNotificationCommand(cmd)
So(err.Error(), ShouldEqual, "time: invalid duration invalid duration")
})
})
Convey("Cannot update alert notifier with send reminder = false", func() {
cmd := &m.CreateAlertNotificationCommand{
Name: "ops update",
Type: "email",
OrgId: 1,
SendReminder: false,
Settings: simplejson.New(),
}
err := CreateAlertNotificationCommand(cmd)
So(err, ShouldBeNil)
updateCmd := &m.UpdateAlertNotificationCommand{
Id: cmd.Result.Id,
SendReminder: true,
}
Convey("and missing frequency", func() {
err := UpdateAlertNotification(updateCmd)
So(err, ShouldEqual, m.ErrNotificationFrequencyNotFound)
})
Convey("invalid frequency", func() {
updateCmd.Frequency = "invalid duration"
err := UpdateAlertNotification(updateCmd)
So(err, ShouldNotBeNil)
So(err.Error(), ShouldEqual, "time: invalid duration invalid duration")
})
})
Convey("Can save Alert Notification", func() {
cmd := &m.CreateAlertNotificationCommand{
Name: "ops",
Type: "email",
OrgId: 1,
SendReminder: true,
Frequency: "10s",
Settings: simplejson.New(),
}
err := CreateAlertNotificationCommand(cmd)
So(err, ShouldBeNil)
So(cmd.Result.Id, ShouldNotEqual, 0)
So(cmd.Result.OrgId, ShouldNotEqual, 0)
So(cmd.Result.Type, ShouldEqual, "email")
So(cmd.Result.Frequency, ShouldEqual, 10*time.Second)
Convey("Cannot save Alert Notification with the same name", func() {
err = CreateAlertNotificationCommand(cmd)
@@ -45,25 +144,42 @@ func TestAlertNotificationSQLAccess(t *testing.T) {
Convey("Can update alert notification", func() {
newCmd := &m.UpdateAlertNotificationCommand{
Name: "NewName",
Type: "webhook",
OrgId: cmd.Result.OrgId,
Settings: simplejson.New(),
Id: cmd.Result.Id,
Name: "NewName",
Type: "webhook",
OrgId: cmd.Result.OrgId,
SendReminder: true,
Frequency: "60s",
Settings: simplejson.New(),
Id: cmd.Result.Id,
}
err := UpdateAlertNotification(newCmd)
So(err, ShouldBeNil)
So(newCmd.Result.Name, ShouldEqual, "NewName")
So(newCmd.Result.Frequency, ShouldEqual, 60*time.Second)
})
Convey("Can update alert notification to disable sending of reminders", func() {
newCmd := &m.UpdateAlertNotificationCommand{
Name: "NewName",
Type: "webhook",
OrgId: cmd.Result.OrgId,
SendReminder: false,
Settings: simplejson.New(),
Id: cmd.Result.Id,
}
err := UpdateAlertNotification(newCmd)
So(err, ShouldBeNil)
So(newCmd.Result.SendReminder, ShouldBeFalse)
})
})
Convey("Can search using an array of ids", func() {
cmd1 := m.CreateAlertNotificationCommand{Name: "nagios", Type: "webhook", OrgId: 1, Settings: simplejson.New()}
cmd2 := m.CreateAlertNotificationCommand{Name: "slack", Type: "webhook", OrgId: 1, Settings: simplejson.New()}
cmd3 := m.CreateAlertNotificationCommand{Name: "ops2", Type: "email", OrgId: 1, Settings: simplejson.New()}
cmd4 := m.CreateAlertNotificationCommand{IsDefault: true, Name: "default", Type: "email", OrgId: 1, Settings: simplejson.New()}
cmd1 := m.CreateAlertNotificationCommand{Name: "nagios", Type: "webhook", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
cmd2 := m.CreateAlertNotificationCommand{Name: "slack", Type: "webhook", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
cmd3 := m.CreateAlertNotificationCommand{Name: "ops2", Type: "email", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
cmd4 := m.CreateAlertNotificationCommand{IsDefault: true, Name: "default", Type: "email", OrgId: 1, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
otherOrg := m.CreateAlertNotificationCommand{Name: "default", Type: "email", OrgId: 2, Settings: simplejson.New()}
otherOrg := m.CreateAlertNotificationCommand{Name: "default", Type: "email", OrgId: 2, SendReminder: true, Frequency: "10s", Settings: simplejson.New()}
So(CreateAlertNotificationCommand(&cmd1), ShouldBeNil)
So(CreateAlertNotificationCommand(&cmd2), ShouldBeNil)
@@ -65,6 +65,13 @@ func addAlertMigrations(mg *Migrator) {
mg.AddMigration("Add column is_default", NewAddColumnMigration(alert_notification, &Column{
Name: "is_default", Type: DB_Bool, Nullable: false, Default: "0",
}))
mg.AddMigration("Add column frequency", NewAddColumnMigration(alert_notification, &Column{
Name: "frequency", Type: DB_BigInt, Nullable: true,
}))
mg.AddMigration("Add column send_reminder", NewAddColumnMigration(alert_notification, &Column{
Name: "send_reminder", Type: DB_Bool, Nullable: true, Default: "0",
}))
mg.AddMigration("add index alert_notification org_id & name", NewAddIndexMigration(alert_notification, alert_notification.Indices[0]))
mg.AddMigration("Update alert table charset", NewTableCharsetMigration("alert", []*Column{
@@ -82,4 +89,22 @@ func addAlertMigrations(mg *Migrator) {
{Name: "type", Type: DB_NVarchar, Length: 255, Nullable: false},
{Name: "settings", Type: DB_Text, Nullable: false},
}))
notification_journal := Table{
Name: "alert_notification_journal",
Columns: []*Column{
{Name: "id", Type: DB_BigInt, IsPrimaryKey: true, IsAutoIncrement: true},
{Name: "org_id", Type: DB_BigInt, Nullable: false},
{Name: "alert_id", Type: DB_BigInt, Nullable: false},
{Name: "notifier_id", Type: DB_BigInt, Nullable: false},
{Name: "sent_at", Type: DB_BigInt, Nullable: false},
{Name: "success", Type: DB_Bool, Nullable: false},
},
Indices: []*Index{
{Cols: []string{"org_id", "alert_id", "notifier_id"}, Type: IndexType},
},
}
mg.AddMigration("create notification_journal table v1", NewAddTableMigration(notification_journal))
mg.AddMigration("add index notification_journal org_id & alert_id & notifier_id", NewAddIndexMigration(notification_journal, notification_journal.Indices[0]))
}
+1 -1
View File
@@ -106,7 +106,7 @@ func (ss *SqlStore) inTransactionWithRetryCtx(ctx context.Context, callback dbTr
if len(sess.events) > 0 {
for _, e := range sess.events {
if err = bus.Publish(e); err != nil {
log.Error(3, "Failed to publish event after commit", err)
log.Error(3, "Failed to publish event after commit. error: %v", err)
}
}
}
+8
View File
@@ -13,11 +13,19 @@ func init() {
bus.AddHandler("sql", GetDataSourceStats)
bus.AddHandler("sql", GetDataSourceAccessStats)
bus.AddHandler("sql", GetAdminStats)
bus.AddHandlerCtx("sql", GetAlertNotifiersUsageStats)
bus.AddHandlerCtx("sql", GetSystemUserCountStats)
}
var activeUserTimeLimit = time.Hour * 24 * 30
func GetAlertNotifiersUsageStats(ctx context.Context, query *m.GetAlertNotifierUsageStatsQuery) error {
var rawSql = `SELECT COUNT(*) as count, type FROM alert_notification GROUP BY type`
query.Result = make([]*m.NotifierUsageStats, 0)
err := x.SQL(rawSql).Find(&query.Result)
return err
}
func GetDataSourceStats(query *m.GetDataSourceStatsQuery) error {
var rawSql = `SELECT COUNT(*) as count, type FROM data_source GROUP BY type`
query.Result = make([]*m.DataSourceStats, 0)
+6
View File
@@ -36,5 +36,11 @@ func TestStatsDataAccess(t *testing.T) {
err := GetDataSourceAccessStats(&query)
So(err, ShouldBeNil)
})
Convey("Get alert notifier stats should not results in error", func() {
query := m.GetAlertNotifierUsageStatsQuery{}
err := GetAlertNotifiersUsageStats(context.Background(), &query)
So(err, ShouldBeNil)
})
})
}
+1 -1
View File
@@ -89,7 +89,7 @@ func inTransactionWithRetryCtx(ctx context.Context, callback dbTransactionFunc,
if len(sess.events) > 0 {
for _, e := range sess.events {
if err = bus.Publish(e); err != nil {
log.Error(3, "Failed to publish event after commit", err)
log.Error(3, "Failed to publish event after commit. error: %v", err)
}
}
}
+20 -3
View File
@@ -164,8 +164,10 @@ var (
Quota QuotaSettings
// Alerting
AlertingEnabled bool
ExecuteAlerts bool
AlertingEnabled bool
ExecuteAlerts bool
AlertingErrorOrTimeout string
AlertingNoDataOrNullValues string
// Explore UI
ExploreEnabled bool
@@ -197,6 +199,7 @@ type Cfg struct {
ImagesDir string
PhantomDir string
RendererUrl string
RendererCallbackUrl string
DisableBruteForceLoginProtection bool
TempDataLifetime time.Duration
@@ -324,7 +327,7 @@ func getCommandLineProperties(args []string) map[string]string {
trimmed := strings.TrimPrefix(arg, "cfg:")
parts := strings.Split(trimmed, "=")
if len(parts) != 2 {
log.Fatal(3, "Invalid command line argument", arg)
log.Fatal(3, "Invalid command line argument. argument: %v", arg)
return nil
}
@@ -641,6 +644,18 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
// Rendering
renderSec := iniFile.Section("rendering")
cfg.RendererUrl = renderSec.Key("server_url").String()
cfg.RendererCallbackUrl = renderSec.Key("callback_url").String()
if cfg.RendererCallbackUrl == "" {
cfg.RendererCallbackUrl = AppUrl
} else {
if cfg.RendererCallbackUrl[len(cfg.RendererCallbackUrl)-1] != '/' {
cfg.RendererCallbackUrl += "/"
}
_, err := url.Parse(cfg.RendererCallbackUrl)
if err != nil {
log.Fatal(4, "Invalid callback_url(%s): %s", cfg.RendererCallbackUrl, err)
}
}
cfg.ImagesDir = filepath.Join(DataPath, "png")
cfg.PhantomDir = filepath.Join(HomePath, "tools/phantomjs")
cfg.TempDataLifetime = iniFile.Section("paths").Key("temp_data_lifetime").MustDuration(time.Second * 3600 * 24)
@@ -659,6 +674,8 @@ func (cfg *Cfg) Load(args *CommandLineArgs) error {
alerting := iniFile.Section("alerting")
AlertingEnabled = alerting.Key("enabled").MustBool(true)
ExecuteAlerts = alerting.Key("execute_alerts").MustBool(true)
AlertingErrorOrTimeout = alerting.Key("error_or_timeout").MustString("alerting")
AlertingNoDataOrNullValues = alerting.Key("nodata_or_nullvalues").MustString("no_data")
explore := iniFile.Section("explore")
ExploreEnabled = explore.Key("enabled").MustBool(false)
+1
View File
@@ -5,6 +5,7 @@ type OAuthInfo struct {
Scopes []string
AuthUrl, TokenUrl string
Enabled bool
EmailAttributeName string
AllowedDomains []string
HostedDomain string
ApiUrl string
+11
View File
@@ -20,6 +20,7 @@ func TestLoadingSettings(t *testing.T) {
So(err, ShouldBeNil)
So(AdminUser, ShouldEqual, "admin")
So(cfg.RendererCallbackUrl, ShouldEqual, "http://localhost:3000/")
})
Convey("Should be able to override via environment variables", func() {
@@ -178,5 +179,15 @@ func TestLoadingSettings(t *testing.T) {
So(InstanceName, ShouldEqual, hostname)
})
Convey("Reading callback_url should add trailing slash", func() {
cfg := NewCfg()
cfg.Load(&CommandLineArgs{
HomePath: "../../",
Args: []string{"cfg:rendering.callback_url=http://myserver/renderer"},
})
So(cfg.RendererCallbackUrl, ShouldEqual, "http://myserver/renderer/")
})
})
}
+4 -2
View File
@@ -20,6 +20,7 @@ type SocialGenericOAuth struct {
allowedOrganizations []string
apiUrl string
allowSignup bool
emailAttributeName string
teamIds []int
}
@@ -264,8 +265,9 @@ func (s *SocialGenericOAuth) extractEmail(data *UserInfoJson) string {
return data.Email
}
if data.Attributes["email:primary"] != nil {
return data.Attributes["email:primary"][0]
emails, ok := data.Attributes[s.emailAttributeName]
if ok && len(emails) != 0 {
return emails[0]
}
if data.Upn != "" {
+17 -15
View File
@@ -60,21 +60,22 @@ func NewOAuthService() {
for _, name := range allOauthes {
sec := setting.Raw.Section("auth." + name)
info := &setting.OAuthInfo{
ClientId: sec.Key("client_id").String(),
ClientSecret: sec.Key("client_secret").String(),
Scopes: util.SplitString(sec.Key("scopes").String()),
AuthUrl: sec.Key("auth_url").String(),
TokenUrl: sec.Key("token_url").String(),
ApiUrl: sec.Key("api_url").String(),
Enabled: sec.Key("enabled").MustBool(),
AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()),
HostedDomain: sec.Key("hosted_domain").String(),
AllowSignup: sec.Key("allow_sign_up").MustBool(),
Name: sec.Key("name").MustString(name),
TlsClientCert: sec.Key("tls_client_cert").String(),
TlsClientKey: sec.Key("tls_client_key").String(),
TlsClientCa: sec.Key("tls_client_ca").String(),
TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(),
ClientId: sec.Key("client_id").String(),
ClientSecret: sec.Key("client_secret").String(),
Scopes: util.SplitString(sec.Key("scopes").String()),
AuthUrl: sec.Key("auth_url").String(),
TokenUrl: sec.Key("token_url").String(),
ApiUrl: sec.Key("api_url").String(),
Enabled: sec.Key("enabled").MustBool(),
EmailAttributeName: sec.Key("email_attribute_name").String(),
AllowedDomains: util.SplitString(sec.Key("allowed_domains").String()),
HostedDomain: sec.Key("hosted_domain").String(),
AllowSignup: sec.Key("allow_sign_up").MustBool(),
Name: sec.Key("name").MustString(name),
TlsClientCert: sec.Key("tls_client_cert").String(),
TlsClientKey: sec.Key("tls_client_key").String(),
TlsClientCa: sec.Key("tls_client_ca").String(),
TlsSkipVerify: sec.Key("tls_skip_verify_insecure").MustBool(),
}
if !info.Enabled {
@@ -153,6 +154,7 @@ func NewOAuthService() {
allowedDomains: info.AllowedDomains,
apiUrl: info.ApiUrl,
allowSignup: info.AllowSignup,
emailAttributeName: info.EmailAttributeName,
teamIds: sec.Key("team_ids").Ints(","),
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
}