dashfolders: merge conflict

This commit is contained in:
Daniel Lee
2018-01-25 14:54:50 +01:00
1824 changed files with 262501 additions and 173759 deletions
+4
View File
@@ -162,6 +162,10 @@ func (hs *HttpServer) newMacaron() *macaron.Macaron {
hs.mapStatic(m, setting.StaticRootPath, "", "public")
hs.mapStatic(m, setting.StaticRootPath, "robots.txt", "robots.txt")
if setting.ImageUploadProvider == "local" {
hs.mapStatic(m, setting.ImagesDir, "", "/public/img/attachments")
}
m.Use(macaron.Renderer(macaron.RenderOptions{
Directory: path.Join(setting.StaticRootPath, "views"),
IndentJSON: macaron.Env != macaron.PROD,
+12 -9
View File
@@ -1,6 +1,7 @@
package api
import (
"context"
"crypto/rand"
"crypto/tls"
"crypto/x509"
@@ -11,7 +12,6 @@ import (
"net/http"
"net/url"
"golang.org/x/net/context"
"golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/bus"
@@ -29,7 +29,7 @@ var (
ErrSignUpNotAllowed = errors.New("Signup is not allowed for this adapter")
ErrUsersQuotaReached = errors.New("Users quota reached")
ErrNoEmail = errors.New("Login provider didn't return an email address")
oauthLogger = log.New("oauth.login")
oauthLogger = log.New("oauth")
)
func GenStateString() string {
@@ -96,7 +96,9 @@ func OAuthLogin(ctx *middleware.Context) {
if setting.OAuthService.OAuthInfos[name].TlsClientCert != "" || setting.OAuthService.OAuthInfos[name].TlsClientKey != "" {
cert, err := tls.LoadX509KeyPair(setting.OAuthService.OAuthInfos[name].TlsClientCert, setting.OAuthService.OAuthInfos[name].TlsClientKey)
if err != nil {
log.Fatal(1, "Failed to setup TlsClientCert", "oauth provider", name, "error", err)
ctx.Logger.Error("Failed to setup TlsClientCert", "oauth", name, "error", err)
ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCert)", nil)
return
}
tr.TLSClientConfig.Certificates = append(tr.TLSClientConfig.Certificates, cert)
@@ -105,7 +107,9 @@ func OAuthLogin(ctx *middleware.Context) {
if setting.OAuthService.OAuthInfos[name].TlsClientCa != "" {
caCert, err := ioutil.ReadFile(setting.OAuthService.OAuthInfos[name].TlsClientCa)
if err != nil {
log.Fatal(1, "Failed to setup TlsClientCa", "oauth provider", name, "error", err)
ctx.Logger.Error("Failed to setup TlsClientCa", "oauth", name, "error", err)
ctx.Handle(500, "login.OAuthLogin(Failed to setup TlsClientCa)", nil)
return
}
caCertPool := x509.NewCertPool()
caCertPool.AppendCertsFromPEM(caCert)
@@ -124,13 +128,13 @@ func OAuthLogin(ctx *middleware.Context) {
// token.TokenType was defaulting to "bearer", which is out of spec, so we explicitly set to "Bearer"
token.TokenType = "Bearer"
ctx.Logger.Debug("OAuthLogin Got token")
oauthLogger.Debug("OAuthLogin Got token", "token", token)
// set up oauth2 client
client := connect.Client(oauthCtx, token)
// get user info
userInfo, err := connect.UserInfo(client)
userInfo, err := connect.UserInfo(client, token)
if err != nil {
if sErr, ok := err.(*social.Error); ok {
redirectWithError(ctx, sErr)
@@ -140,7 +144,7 @@ func OAuthLogin(ctx *middleware.Context) {
return
}
ctx.Logger.Debug("OAuthLogin got user info", "userInfo", userInfo)
oauthLogger.Debug("OAuthLogin got user info", "userInfo", userInfo)
// validate that we got at least an email address
if userInfo.Email == "" {
@@ -205,8 +209,7 @@ func OAuthLogin(ctx *middleware.Context) {
}
func redirectWithError(ctx *middleware.Context, err error, v ...interface{}) {
ctx.Logger.Info(err.Error(), v...)
// TODO: we can use the flash storage here once it's implemented
ctx.Logger.Error(err.Error(), v...)
ctx.Session.Set("loginError", err.Error())
ctx.Redirect(setting.AppSubUrl + "/login")
}
@@ -88,6 +88,8 @@ func NewImageUploader() (ImageUploader, error) {
container_name := azureBlobSec.Key("container_name").MustString("")
return NewAzureBlobUploader(account_name, account_key, container_name), nil
case "local":
return NewLocalImageUploader()
}
if setting.ImageUploadProvider != "" {
@@ -143,5 +143,23 @@ func TestImageUploaderFactory(t *testing.T) {
So(original.container_name, ShouldEqual, "container_name")
})
})
Convey("Local uploader", func() {
var err error
setting.NewConfigContext(&setting.CommandLineArgs{
HomePath: "../../../",
})
setting.ImageUploadProvider = "local"
uploader, err := NewImageUploader()
So(err, ShouldBeNil)
original, ok := uploader.(*LocalUploader)
So(ok, ShouldBeTrue)
So(original, ShouldNotBeNil)
})
})
}
@@ -0,0 +1,22 @@
package imguploader
import (
"context"
"path"
"path/filepath"
"github.com/grafana/grafana/pkg/setting"
)
type LocalUploader struct {
}
func (u *LocalUploader) Upload(ctx context.Context, imageOnDiskPath string) (string, error) {
filename := filepath.Base(imageOnDiskPath)
image_url := setting.ToAbsUrl(path.Join("public/img/attachments", filename))
return image_url, nil
}
func NewLocalImageUploader() (*LocalUploader, error) {
return &LocalUploader{}, nil
}
@@ -0,0 +1,18 @@
package imguploader
import (
"context"
"testing"
. "github.com/smartystreets/goconvey/convey"
)
func TestUploadToLocal(t *testing.T) {
Convey("[Integration test] for external_image_store.local", t, func() {
localUploader, _ := NewLocalImageUploader()
path, err := localUploader.Upload(context.Background(), "../../../public/img/logo_transparent_400x.png")
So(err, ShouldBeNil)
So(path, ShouldContainSubstring, "/public/img/attachments")
})
}
+2 -1
View File
@@ -26,9 +26,10 @@ import (
"strings"
"time"
"context"
"github.com/prometheus/common/expfmt"
"github.com/prometheus/common/model"
"golang.org/x/net/context"
dto "github.com/prometheus/client_model/go"
+1
View File
@@ -379,6 +379,7 @@ func sendUsageStats() {
metrics["stats.alerts.count"] = statsQuery.Result.Alerts
metrics["stats.active_users.count"] = statsQuery.Result.ActiveUsers
metrics["stats.datasources.count"] = statsQuery.Result.Datasources
metrics["stats.stars.count"] = statsQuery.Result.Stars
dsStats := models.GetDataSourceStatsQuery{}
if err := bus.Dispatch(&dsStats); err != nil {
+1
View File
@@ -8,6 +8,7 @@ type SystemStats struct {
Orgs int64
Playlists int64
Alerts int64
Stars int64
}
type DataSourceStats struct {
-22
View File
@@ -1,22 +0,0 @@
package tsdb
import (
proto "github.com/grafana/grafana/pkg/tsdb/models"
"golang.org/x/net/context"
)
type GRPCClient struct {
proto.TsdbPluginClient
}
func (m *GRPCClient) Query(ctx context.Context, req *proto.TsdbQuery) (*proto.Response, error) {
return m.TsdbPluginClient.Query(ctx, req)
}
type GRPCServer struct {
TsdbPlugin
}
func (m *GRPCServer) Query(ctx context.Context, req *proto.TsdbQuery) (*proto.Response, error) {
return m.TsdbPlugin.Query(ctx, req)
}
-27
View File
@@ -1,27 +0,0 @@
package tsdb
import (
"golang.org/x/net/context"
proto "github.com/grafana/grafana/pkg/tsdb/models"
plugin "github.com/hashicorp/go-plugin"
"google.golang.org/grpc"
)
type TsdbPlugin interface {
Query(ctx context.Context, req *proto.TsdbQuery) (*proto.Response, error)
}
type TsdbPluginImpl struct {
plugin.NetRPCUnsupportedPlugin
Plugin TsdbPlugin
}
func (p *TsdbPluginImpl) GRPCServer(s *grpc.Server) error {
proto.RegisterTsdbPluginServer(s, &GRPCServer{p.Plugin})
return nil
}
func (p *TsdbPluginImpl) GRPCClient(c *grpc.ClientConn) (interface{}, error) {
return &GRPCClient{proto.NewTsdbPluginClient(c)}, nil
}
@@ -1,22 +1,23 @@
package tsdb
package wrapper
import (
"context"
"errors"
"fmt"
"github.com/grafana/grafana/pkg/components/null"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
"github.com/grafana/grafana/pkg/tsdb"
proto "github.com/grafana/grafana/pkg/tsdb/models"
"golang.org/x/net/context"
"github.com/grafana/grafana_plugin_model/go/datasource"
)
func NewDatasourcePluginWrapper(log log.Logger, plugin TsdbPlugin) *DatasourcePluginWrapper {
return &DatasourcePluginWrapper{TsdbPlugin: plugin, logger: log}
func NewDatasourcePluginWrapper(log log.Logger, plugin datasource.DatasourcePlugin) *DatasourcePluginWrapper {
return &DatasourcePluginWrapper{DatasourcePlugin: plugin, logger: log}
}
type DatasourcePluginWrapper struct {
TsdbPlugin
datasource.DatasourcePlugin
logger log.Logger
}
@@ -26,28 +27,29 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
return nil, err
}
pbQuery := &proto.TsdbQuery{
Datasource: &proto.DatasourceInfo{
JsonData: string(jsonData),
Name: ds.Name,
Type: ds.Type,
Url: ds.Url,
Id: ds.Id,
OrgId: ds.OrgId,
pbQuery := &datasource.DatasourceRequest{
Datasource: &datasource.DatasourceInfo{
Name: ds.Name,
Type: ds.Type,
Url: ds.Url,
Id: ds.Id,
OrgId: ds.OrgId,
JsonData: string(jsonData),
DecryptedSecureJsonData: ds.SecureJsonData.Decrypt(),
},
TimeRange: &proto.TimeRange{
TimeRange: &datasource.TimeRange{
FromRaw: query.TimeRange.From,
ToRaw: query.TimeRange.To,
ToEpochMs: query.TimeRange.GetToAsMsEpoch(),
FromEpochMs: query.TimeRange.GetFromAsMsEpoch(),
},
Queries: []*proto.Query{},
Queries: []*datasource.Query{},
}
for _, q := range query.Queries {
modelJson, _ := q.Model.MarshalJSON()
pbQuery.Queries = append(pbQuery.Queries, &proto.Query{
pbQuery.Queries = append(pbQuery.Queries, &datasource.Query{
ModelJson: string(modelJson),
IntervalMs: q.IntervalMs,
RefId: q.RefId,
@@ -55,7 +57,7 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
})
}
pbres, err := tw.TsdbPlugin.Query(ctx, pbQuery)
pbres, err := tw.DatasourcePlugin.Query(ctx, pbQuery)
if err != nil {
return nil, err
@@ -66,9 +68,11 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
}
for _, r := range pbres.Results {
res.Results[r.RefId] = &tsdb.QueryResult{
RefId: r.RefId,
Series: []*tsdb.TimeSeries{},
qr := &tsdb.QueryResult{
RefId: r.RefId,
Series: []*tsdb.TimeSeries{},
Error: errors.New(r.Error),
ErrorString: r.Error,
}
for _, s := range r.GetSeries() {
@@ -79,7 +83,7 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
points = append(points, po)
}
res.Results[r.RefId].Series = append(res.Results[r.RefId].Series, &tsdb.TimeSeries{
qr.Series = append(qr.Series, &tsdb.TimeSeries{
Name: s.Name,
Tags: s.Tags,
Points: points,
@@ -90,12 +94,14 @@ func (tw *DatasourcePluginWrapper) Query(ctx context.Context, ds *models.DataSou
if err != nil {
return nil, err
}
res.Results[r.RefId].Tables = mappedTables
qr.Tables = mappedTables
res.Results[r.RefId] = qr
}
return res, nil
}
func (tw *DatasourcePluginWrapper) mapTables(r *proto.QueryResult) ([]*tsdb.Table, error) {
func (tw *DatasourcePluginWrapper) mapTables(r *datasource.QueryResult) ([]*tsdb.Table, error) {
var tables []*tsdb.Table
for _, t := range r.GetTables() {
mappedTable, err := tw.mapTable(t)
@@ -107,7 +113,7 @@ func (tw *DatasourcePluginWrapper) mapTables(r *proto.QueryResult) ([]*tsdb.Tabl
return tables, nil
}
func (tw *DatasourcePluginWrapper) mapTable(t *proto.Table) (*tsdb.Table, error) {
func (tw *DatasourcePluginWrapper) mapTable(t *datasource.Table) (*tsdb.Table, error) {
table := &tsdb.Table{}
for _, c := range t.GetColumns() {
table.Columns = append(table.Columns, tsdb.TableColumn{
@@ -130,19 +136,19 @@ func (tw *DatasourcePluginWrapper) mapTable(t *proto.Table) (*tsdb.Table, error)
return table, nil
}
func (tw *DatasourcePluginWrapper) mapRowValue(rv *proto.RowValue) (interface{}, error) {
func (tw *DatasourcePluginWrapper) mapRowValue(rv *datasource.RowValue) (interface{}, error) {
switch rv.Kind {
case proto.RowValue_TYPE_NULL:
case datasource.RowValue_TYPE_NULL:
return nil, nil
case proto.RowValue_TYPE_INT64:
case datasource.RowValue_TYPE_INT64:
return rv.Int64Value, nil
case proto.RowValue_TYPE_BOOL:
case datasource.RowValue_TYPE_BOOL:
return rv.BoolValue, nil
case proto.RowValue_TYPE_STRING:
case datasource.RowValue_TYPE_STRING:
return rv.StringValue, nil
case proto.RowValue_TYPE_DOUBLE:
case datasource.RowValue_TYPE_DOUBLE:
return rv.DoubleValue, nil
case proto.RowValue_TYPE_BYTES:
case datasource.RowValue_TYPE_BYTES:
return rv.BytesValue, nil
default:
return nil, fmt.Errorf("Unsupported row value %v from plugin", rv.Kind)
@@ -1,17 +1,18 @@
package tsdb
package wrapper
import (
"testing"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana/pkg/tsdb/models"
"testing"
"github.com/grafana/grafana_plugin_model/go/datasource"
)
func TestMapTables(t *testing.T) {
dpw := NewDatasourcePluginWrapper(log.New("test-logger"), nil)
var qr = &proto.QueryResult{}
qr.Tables = append(qr.Tables, &proto.Table{
Columns: []*proto.TableColumn{},
var qr = &datasource.QueryResult{}
qr.Tables = append(qr.Tables, &datasource.Table{
Columns: []*datasource.TableColumn{},
Rows: nil,
})
want := []*tsdb.Table{{}}
@@ -28,16 +29,16 @@ func TestMapTables(t *testing.T) {
func TestMapTable(t *testing.T) {
dpw := NewDatasourcePluginWrapper(log.New("test-logger"), nil)
source := &proto.Table{
Columns: []*proto.TableColumn{{Name: "column1"}, {Name: "column2"}},
Rows: []*proto.TableRow{{
Values: []*proto.RowValue{
source := &datasource.Table{
Columns: []*datasource.TableColumn{{Name: "column1"}, {Name: "column2"}},
Rows: []*datasource.TableRow{{
Values: []*datasource.RowValue{
{
Kind: proto.RowValue_TYPE_BOOL,
Kind: datasource.RowValue_TYPE_BOOL,
BoolValue: true,
},
{
Kind: proto.RowValue_TYPE_INT64,
Kind: datasource.RowValue_TYPE_INT64,
Int64Value: 42,
},
},
@@ -71,37 +72,37 @@ func TestMapTable(t *testing.T) {
func TestMappingRowValue(t *testing.T) {
dpw := NewDatasourcePluginWrapper(log.New("test-logger"), nil)
boolRowValue, _ := dpw.mapRowValue(&proto.RowValue{Kind: proto.RowValue_TYPE_BOOL, BoolValue: true})
boolRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_BOOL, BoolValue: true})
haveBool, ok := boolRowValue.(bool)
if !ok || haveBool != true {
t.Fatalf("Expected true, was %s", haveBool)
}
intRowValue, _ := dpw.mapRowValue(&proto.RowValue{Kind: proto.RowValue_TYPE_INT64, Int64Value: 42})
intRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_INT64, Int64Value: 42})
haveInt, ok := intRowValue.(int64)
if !ok || haveInt != 42 {
t.Fatalf("Expected %d, was %d", 42, haveInt)
}
stringRowValue, _ := dpw.mapRowValue(&proto.RowValue{Kind: proto.RowValue_TYPE_STRING, StringValue: "grafana"})
stringRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_STRING, StringValue: "grafana"})
haveString, ok := stringRowValue.(string)
if !ok || haveString != "grafana" {
t.Fatalf("Expected %s, was %s", "grafana", haveString)
}
doubleRowValue, _ := dpw.mapRowValue(&proto.RowValue{Kind: proto.RowValue_TYPE_DOUBLE, DoubleValue: 1.5})
doubleRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_DOUBLE, DoubleValue: 1.5})
haveDouble, ok := doubleRowValue.(float64)
if !ok || haveDouble != 1.5 {
t.Fatalf("Expected %v, was %v", 1.5, haveDouble)
}
bytesRowValue, _ := dpw.mapRowValue(&proto.RowValue{Kind: proto.RowValue_TYPE_BYTES, BytesValue: []byte{66}})
bytesRowValue, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_BYTES, BytesValue: []byte{66}})
haveBytes, ok := bytesRowValue.([]byte)
if !ok || len(haveBytes) != 1 || haveBytes[0] != 66 {
t.Fatalf("Expected %v, was %v", []byte{66}, haveBytes)
}
haveNil, _ := dpw.mapRowValue(&proto.RowValue{Kind: proto.RowValue_TYPE_NULL})
haveNil, _ := dpw.mapRowValue(&datasource.RowValue{Kind: datasource.RowValue_TYPE_NULL})
if haveNil != nil {
t.Fatalf("Expected %v, was %v", nil, haveNil)
}
+5 -4
View File
@@ -14,8 +14,9 @@ import (
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/models"
shared "github.com/grafana/grafana/pkg/plugins/datasource/tsdb"
"github.com/grafana/grafana/pkg/plugins/datasource/wrapper"
"github.com/grafana/grafana/pkg/tsdb"
"github.com/grafana/grafana_plugin_model/go/datasource"
plugin "github.com/hashicorp/go-plugin"
)
@@ -92,7 +93,7 @@ func (p *DataSourcePlugin) spawnSubProcess() error {
p.client = plugin.NewClient(&plugin.ClientConfig{
HandshakeConfig: handshakeConfig,
Plugins: map[string]plugin.Plugin{p.Id: &shared.TsdbPluginImpl{}},
Plugins: map[string]plugin.Plugin{p.Id: &datasource.DatasourcePluginImpl{}},
Cmd: exec.Command(fullpath),
AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC},
Logger: LogWrapper{Logger: p.log},
@@ -108,10 +109,10 @@ func (p *DataSourcePlugin) spawnSubProcess() error {
return err
}
plugin := raw.(shared.TsdbPlugin)
plugin := raw.(datasource.DatasourcePlugin)
tsdb.RegisterTsdbQueryEndpoint(p.Id, func(dsInfo *models.DataSource) (tsdb.TsdbQueryEndpoint, error) {
return shared.NewDatasourcePluginWrapper(p.log, plugin), nil
return wrapper.NewDatasourcePluginWrapper(p.log, plugin), nil
})
return nil
+1 -1
View File
@@ -20,7 +20,7 @@ func init() {
OptionsTemplate: `
<h3 class="page-heading">Email addresses</h3>
<div class="gf-form">
<textarea rows="7" class="gf-form-input width-25" required ng-model="ctrl.model.settings.addresses"></textarea>
<textarea rows="7" class="gf-form-input width-27" required ng-model="ctrl.model.settings.addresses"></textarea>
</div>
<div class="gf-form">
<span>You can enter multiple email addresses using a ";" separator</span>
+12 -2
View File
@@ -23,6 +23,10 @@ func init() {
<span class="gf-form-label width-14">API Key</span>
<input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.apiKey" placeholder="OpsGenie API Key"></input>
</div>
<div class="gf-form">
<span class="gf-form-label width-14">Alert API Url</span>
<input type="text" required class="gf-form-input max-width-22" ng-model="ctrl.model.settings.apiUrl" placeholder="https://api.opsgenie.com/v2/alerts"></input>
</div>
<div class="gf-form">
<gf-form-switch
class="gf-form"
@@ -43,13 +47,18 @@ var (
func NewOpsGenieNotifier(model *m.AlertNotification) (alerting.Notifier, error) {
autoClose := model.Settings.Get("autoClose").MustBool(true)
apiKey := model.Settings.Get("apiKey").MustString()
apiUrl := model.Settings.Get("apiUrl").MustString()
if apiKey == "" {
return nil, alerting.ValidationError{Reason: "Could not find api key property in settings"}
}
if apiUrl == "" {
apiUrl = opsgenieAlertURL
}
return &OpsGenieNotifier{
NotifierBase: NewNotifierBase(model.Id, model.IsDefault, model.Name, model.Type, model.Settings),
ApiKey: apiKey,
ApiUrl: apiUrl,
AutoClose: autoClose,
log: log.New("alerting.notifier.opsgenie"),
}, nil
@@ -58,6 +67,7 @@ func NewOpsGenieNotifier(model *m.AlertNotification) (alerting.Notifier, error)
type OpsGenieNotifier struct {
NotifierBase
ApiKey string
ApiUrl string
AutoClose bool
log log.Logger
}
@@ -105,7 +115,7 @@ func (this *OpsGenieNotifier) createAlert(evalContext *alerting.EvalContext) err
body, _ := bodyJSON.MarshalJSON()
cmd := &m.SendWebhookSync{
Url: opsgenieAlertURL,
Url: this.ApiUrl,
Body: string(body),
HttpMethod: "POST",
HttpHeader: map[string]string{
@@ -129,7 +139,7 @@ func (this *OpsGenieNotifier) closeAlert(evalContext *alerting.EvalContext) erro
body, _ := bodyJSON.MarshalJSON()
cmd := &m.SendWebhookSync{
Url: fmt.Sprintf("%s/alertId-%d/close?identifierType=alias", opsgenieAlertURL, evalContext.Rule.Id),
Url: fmt.Sprintf("%s/alertId-%d/close?identifierType=alias", this.ApiUrl, evalContext.Rule.Id),
Body: string(body),
HttpMethod: "POST",
HttpHeader: map[string]string{
@@ -5,20 +5,25 @@ import (
"path/filepath"
"strings"
"github.com/grafana/grafana/pkg/log"
yaml "gopkg.in/yaml.v2"
)
type configReader struct {
path string
log log.Logger
}
func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
var dashboards []*DashboardsAsConfig
files, err := ioutil.ReadDir(cr.path)
if err != nil {
return nil, err
cr.log.Error("cant read dashboard provisioning files from directory", "path", cr.path)
return dashboards, nil
}
var dashboards []*DashboardsAsConfig
for _, file := range files {
if !strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml") {
continue
@@ -30,13 +35,13 @@ func (cr *configReader) readConfig() ([]*DashboardsAsConfig, error) {
return nil, err
}
var datasource []*DashboardsAsConfig
err = yaml.Unmarshal(yamlFile, &datasource)
var dashCfg []*DashboardsAsConfig
err = yaml.Unmarshal(yamlFile, &dashCfg)
if err != nil {
return nil, err
}
dashboards = append(dashboards, datasource...)
dashboards = append(dashboards, dashCfg...)
}
for i := range dashboards {
@@ -3,6 +3,7 @@ package dashboards
import (
"testing"
"github.com/grafana/grafana/pkg/log"
. "github.com/smartystreets/goconvey/convey"
)
@@ -16,8 +17,8 @@ func TestDashboardsAsConfig(t *testing.T) {
Convey("Can read config file", func() {
cfgProvifer := configReader{path: simpleDashboardConfig}
cfg, err := cfgProvifer.readConfig()
cfgProvider := configReader{path: simpleDashboardConfig, log: log.New("test-logger")}
cfg, err := cfgProvider.readConfig()
if err != nil {
t.Fatalf("readConfig return an error %v", err)
}
@@ -33,7 +34,7 @@ func TestDashboardsAsConfig(t *testing.T) {
So(ds.Editable, ShouldBeTrue)
So(len(ds.Options), ShouldEqual, 1)
So(ds.Options["folder"], ShouldEqual, "/var/lib/grafana/dashboards")
So(ds.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
ds2 := cfg[1]
@@ -44,19 +45,29 @@ func TestDashboardsAsConfig(t *testing.T) {
So(ds2.Editable, ShouldBeFalse)
So(len(ds2.Options), ShouldEqual, 1)
So(ds2.Options["folder"], ShouldEqual, "/var/lib/grafana/dashboards")
So(ds2.Options["path"], ShouldEqual, "/var/lib/grafana/dashboards")
})
Convey("Should skip broken config files", func() {
Convey("Should skip invalid path", func() {
cfgProvifer := configReader{path: brokenConfigs}
cfg, err := cfgProvifer.readConfig()
cfgProvider := configReader{path: "/invalid-directory", log: log.New("test-logger")}
cfg, err := cfgProvider.readConfig()
if err != nil {
t.Fatalf("readConfig return an error %v", err)
}
So(len(cfg), ShouldEqual, 0)
})
Convey("Should skip broken config files", func() {
cfgProvider := configReader{path: brokenConfigs, log: log.New("test-logger")}
cfg, err := cfgProvider.readConfig()
if err != nil {
t.Fatalf("readConfig return an error %v", err)
}
So(len(cfg), ShouldEqual, 0)
})
})
}
@@ -14,9 +14,10 @@ type DashboardProvisioner struct {
}
func Provision(ctx context.Context, configDirectory string) (*DashboardProvisioner, error) {
log := log.New("provisioning.dashboard")
d := &DashboardProvisioner{
cfgReader: &configReader{path: configDirectory},
log: log.New("provisioning.dashboard"),
cfgReader: &configReader{path: configDirectory, log: log},
log: log,
ctx: ctx,
}
@@ -34,9 +34,15 @@ type fileReader struct {
}
func NewDashboardFileReader(cfg *DashboardsAsConfig, log log.Logger) (*fileReader, error) {
path, ok := cfg.Options["folder"].(string)
var path string
path, ok := cfg.Options["path"].(string)
if !ok {
return nil, fmt.Errorf("Failed to load dashboards. folder param is not a string")
path, ok = cfg.Options["folder"].(string)
if !ok {
return nil, fmt.Errorf("Failed to load dashboards. path param is not a string")
}
log.Warn("[Deprecated] The folder property is deprecated. Please use path instead.")
}
if _, err := os.Stat(path); os.IsNotExist(err) {
@@ -145,6 +151,17 @@ func createWalkFn(fr *fileReader, folderId int64) filepath.WalkFunc {
return nil
}
checkFilepath, err := filepath.EvalSymlinks(path)
if path != checkFilepath {
path = checkFilepath
fi, err := os.Lstat(checkFilepath)
if err != nil {
return err
}
fileInfo = fi
}
cachedDashboard, exist := fr.cache.getCache(path)
if exist && cachedDashboard.UpdatedAt == fileInfo.ModTime() {
return nil
@@ -42,7 +42,7 @@ func TestDashboardFileReader(t *testing.T) {
}
Convey("Can read default dashboard", func() {
cfg.Options["folder"] = defaultDashboards
cfg.Options["path"] = defaultDashboards
cfg.Folder = "Team A"
reader, err := NewDashboardFileReader(cfg, logger)
@@ -67,7 +67,7 @@ func TestDashboardFileReader(t *testing.T) {
})
Convey("Should not update dashboards when db is newer", func() {
cfg.Options["folder"] = oneDashboard
cfg.Options["path"] = oneDashboard
fakeRepo.getDashboard = append(fakeRepo.getDashboard, &models.Dashboard{
Updated: time.Now().Add(time.Hour),
@@ -84,7 +84,7 @@ func TestDashboardFileReader(t *testing.T) {
})
Convey("Can read default dashboard and replace old version in database", func() {
cfg.Options["folder"] = oneDashboard
cfg.Options["path"] = oneDashboard
stat, _ := os.Stat(oneDashboard + "/dashboard1.json")
@@ -115,7 +115,7 @@ func TestDashboardFileReader(t *testing.T) {
})
Convey("Broken dashboards should not cause error", func() {
cfg.Options["folder"] = brokenDashboards
cfg.Options["path"] = brokenDashboards
_, err := NewDashboardFileReader(cfg, logger)
So(err, ShouldBeNil)
@@ -167,7 +167,7 @@ func TestDashboardFileReader(t *testing.T) {
OrgId: 1,
Folder: "",
Options: map[string]interface{}{
"folder": defaultDashboards,
"path": defaultDashboards,
},
}
@@ -184,6 +184,30 @@ func TestDashboardFileReader(t *testing.T) {
So(shouldSkip, ShouldBeNil)
})
})
Convey("Can use bpth path and folder as dashboard path", func() {
cfg := &DashboardsAsConfig{
Name: "Default",
Type: "file",
OrgId: 1,
Folder: "",
Options: map[string]interface{}{},
}
Convey("using path parameter", func() {
cfg.Options["path"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
So(err, ShouldBeNil)
So(reader.Path, ShouldEqual, defaultDashboards)
})
Convey("using folder as options", func() {
cfg.Options["folder"] = defaultDashboards
reader, err := NewDashboardFileReader(cfg, log.New("test-logger"))
So(err, ShouldBeNil)
So(reader.Path, ShouldEqual, defaultDashboards)
})
})
})
}
@@ -4,9 +4,9 @@
editable: true
type: file
options:
folder: /var/lib/grafana/dashboards
path: /var/lib/grafana/dashboards
- name: 'default'
type: file
options:
folder: /var/lib/grafana/dashboards
path: /var/lib/grafana/dashboards
@@ -25,13 +25,13 @@ func Provision(configDirectory string) error {
type DatasourceProvisioner struct {
log log.Logger
cfgProvider configReader
cfgProvider *configReader
}
func newDatasourceProvisioner(log log.Logger) DatasourceProvisioner {
return DatasourceProvisioner{
log: log,
cfgProvider: configReader{},
cfgProvider: &configReader{log: log},
}
}
@@ -95,15 +95,19 @@ func (dc *DatasourceProvisioner) deleteDatasources(dsToDelete []*DeleteDatasourc
return nil
}
type configReader struct{}
type configReader struct {
log log.Logger
}
func (cr *configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
var datasources []*DatasourcesAsConfig
func (configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
files, err := ioutil.ReadDir(path)
if err != nil {
return nil, err
cr.log.Error("cant read datasource provisioning files from directory", "path", path)
return datasources, nil
}
var datasources []*DatasourcesAsConfig
for _, file := range files {
if strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml") {
filename, _ := filepath.Abs(filepath.Join(path, file.Name()))
@@ -11,7 +11,7 @@ import (
)
var (
logger log.Logger = log.New("fake.logger")
logger log.Logger = log.New("fake.log")
oneDatasourcesConfig string = ""
twoDatasourcesConfig string = "./test-configs/two-datasources"
twoDatasourcesConfigPurgeOthers string = "./test-configs/insert-two-delete-two"
@@ -115,12 +115,23 @@ func TestDatasourceAsConfig(t *testing.T) {
})
Convey("broken yaml should return error", func() {
_, err := configReader{}.readConfig(brokenYaml)
reader := &configReader{}
_, err := reader.readConfig(brokenYaml)
So(err, ShouldNotBeNil)
})
Convey("skip invalid directory", func() {
cfgProvifer := &configReader{log: log.New("test logger")}
cfg, err := cfgProvifer.readConfig("./invalid-directory")
if err != nil {
t.Fatalf("readConfig return an error %v", err)
}
So(len(cfg), ShouldEqual, 0)
})
Convey("can read all properties", func() {
cfgProvifer := configReader{}
cfgProvifer := &configReader{log: log.New("test logger")}
cfg, err := cfgProvifer.readConfig(allProperties)
if err != nil {
t.Fatalf("readConfig return an error %v", err)
+4 -4
View File
@@ -24,7 +24,7 @@ func init() {
func GetAlertById(query *m.GetAlertByIdQuery) error {
alert := m.Alert{}
has, err := x.Id(query.Id).Get(&alert)
has, err := x.ID(query.Id).Get(&alert)
if !has {
return fmt.Errorf("could not find alert")
}
@@ -113,7 +113,7 @@ func HandleAlertsQuery(query *m.GetAlertsQuery) error {
}
alerts := make([]*m.Alert, 0)
if err := x.Sql(sql.String(), params...).Find(&alerts); err != nil {
if err := x.SQL(sql.String(), params...).Find(&alerts); err != nil {
return err
}
@@ -260,7 +260,7 @@ func SetAlertState(cmd *m.SetAlertStateCommand) error {
alert.ExecutionError = cmd.Error
}
sess.Id(alert.Id).Update(&alert)
sess.ID(alert.Id).Update(&alert)
return nil
})
}
@@ -324,7 +324,7 @@ func GetAlertStatesForDashboard(query *m.GetAlertStatesForDashboardQuery) error
WHERE org_id = ? AND dashboard_id = ?`
query.Result = make([]*m.AlertStateInfoDTO, 0)
err := x.Sql(rawSql, query.OrgId, query.DashboardId).Find(&query.Result)
err := x.SQL(rawSql, query.OrgId, query.DashboardId).Find(&query.Result)
return err
}
+3 -3
View File
@@ -76,7 +76,7 @@ func GetAlertNotificationsToSend(query *m.GetAlertNotificationsToSendQuery) erro
sql.WriteString(`)`)
results := make([]*m.AlertNotification, 0)
if err := x.Sql(sql.String(), params...).Find(&results); err != nil {
if err := x.SQL(sql.String(), params...).Find(&results); err != nil {
return err
}
@@ -165,7 +165,7 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
return inTransaction(func(sess *DBSession) (err error) {
current := m.AlertNotification{}
if _, err = sess.Id(cmd.Id).Get(&current); err != nil {
if _, err = sess.ID(cmd.Id).Get(&current); err != nil {
return err
}
@@ -187,7 +187,7 @@ func UpdateAlertNotification(cmd *m.UpdateAlertNotificationCommand) error {
sess.UseBool("is_default")
if affected, err := sess.Id(cmd.Id).Update(current); err != nil {
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")
+1
View File
@@ -176,6 +176,7 @@ func GetDashboardAclInfoList(query *m.GetDashboardAclInfoListQuery) error {
folder.has_acl = ` + dialect.BooleanStr(false) + `
) AND
da.dashboard_id = -1
ORDER BY 1 ASC
`
query.Result = make([]*m.DashboardAclInfoDTO, 0)
+3 -6
View File
@@ -17,15 +17,13 @@ func init() {
}
func CreatePlaylist(cmd *m.CreatePlaylistCommand) error {
var err error
playlist := m.Playlist{
Name: cmd.Name,
Interval: cmd.Interval,
OrgId: cmd.OrgId,
}
_, err = x.Insert(&playlist)
_, err := x.Insert(&playlist)
fmt.Printf("%v", playlist.Id)
@@ -47,7 +45,6 @@ func CreatePlaylist(cmd *m.CreatePlaylistCommand) error {
}
func UpdatePlaylist(cmd *m.UpdatePlaylistCommand) error {
var err error
playlist := m.Playlist{
Id: cmd.Id,
OrgId: cmd.OrgId,
@@ -68,7 +65,7 @@ func UpdatePlaylist(cmd *m.UpdatePlaylistCommand) error {
Interval: playlist.Interval,
}
_, err = x.Id(cmd.Id).Cols("id", "name", "interval").Update(&playlist)
_, err := x.ID(cmd.Id).Cols("id", "name", "interval").Update(&playlist)
if err != nil {
return err
@@ -104,7 +101,7 @@ func GetPlaylist(query *m.GetPlaylistByIdQuery) error {
}
playlist := m.Playlist{}
_, err := x.Id(query.Id).Get(&playlist)
_, err := x.ID(query.Id).Get(&playlist)
query.Result = &playlist
+64 -61
View File
@@ -18,7 +18,7 @@ var activeUserTimeLimit time.Duration = time.Hour * 24 * 30
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)
err := x.Sql(rawSql).Find(&query.Result)
err := x.SQL(rawSql).Find(&query.Result)
if err != nil {
return err
}
@@ -30,36 +30,39 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
var rawSql = `SELECT
(
SELECT COUNT(*)
FROM ` + dialect.Quote("user") + `
) AS users,
FROM ` + dialect.Quote("user") + `
) AS users,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("org") + `
) AS orgs,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
) AS dashboards,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("data_source") + `
) AS datasources,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("playlist") + `
) AS playlists,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("alert") + `
) AS alerts,
FROM ` + dialect.Quote("org") + `
) AS orgs,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
) AS dashboards,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("data_source") + `
) AS datasources,
(
SELECT COUNT(*) FROM ` + dialect.Quote("star") + `
) AS stars,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("playlist") + `
) AS playlists,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("alert") + `
) AS alerts,
(
SELECT COUNT(*) FROM ` + dialect.Quote("user") + ` where last_seen_at > ?
) as active_users
) as active_users
`
activeUserDeadlineDate := time.Now().Add(-activeUserTimeLimit)
var stats m.SystemStats
_, err := x.Sql(rawSql, activeUserDeadlineDate).Get(&stats)
_, err := x.SQL(rawSql, activeUserDeadlineDate).Get(&stats)
if err != nil {
return err
}
@@ -70,51 +73,51 @@ func GetSystemStats(query *m.GetSystemStatsQuery) error {
func GetAdminStats(query *m.GetAdminStatsQuery) error {
var rawSql = `SELECT
(
SELECT COUNT(*)
FROM ` + dialect.Quote("user") + `
) AS users,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("org") + `
) AS orgs,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
) AS dashboards,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard_snapshot") + `
) AS snapshots,
(
SELECT COUNT( DISTINCT ( ` + dialect.Quote("term") + ` ))
FROM ` + dialect.Quote("dashboard_tag") + `
) AS tags,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("data_source") + `
) AS datasources,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("playlist") + `
) AS playlists,
(
SELECT COUNT(*) FROM ` + dialect.Quote("star") + `
) AS stars,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("alert") + `
) AS alerts,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("user") + `
) AS users,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("org") + `
) AS orgs,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard") + `
) AS dashboards,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("dashboard_snapshot") + `
) AS snapshots,
(
SELECT COUNT( DISTINCT ( ` + dialect.Quote("term") + ` ))
FROM ` + dialect.Quote("dashboard_tag") + `
) AS tags,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("data_source") + `
) AS datasources,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("playlist") + `
) AS playlists,
(
SELECT COUNT(*) FROM ` + dialect.Quote("star") + `
) AS stars,
(
SELECT COUNT(*)
FROM ` + dialect.Quote("alert") + `
) AS alerts,
(
SELECT COUNT(*)
from ` + dialect.Quote("user") + ` where last_seen_at > ?
from ` + dialect.Quote("user") + ` where last_seen_at > ?
) as active_users
`
`
activeUserDeadlineDate := time.Now().Add(-activeUserTimeLimit)
var stats m.AdminStats
_, err := x.Sql(rawSql, activeUserDeadlineDate).Get(&stats)
_, err := x.SQL(rawSql, activeUserDeadlineDate).Get(&stats)
if err != nil {
return err
}
+120 -47
View File
@@ -1,18 +1,21 @@
package social
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net/http"
"net/mail"
"regexp"
"github.com/grafana/grafana/pkg/models"
"golang.org/x/oauth2"
)
type GenericOAuth struct {
*oauth2.Config
type SocialGenericOAuth struct {
*SocialBase
allowedDomains []string
allowedOrganizations []string
apiUrl string
@@ -20,19 +23,19 @@ type GenericOAuth struct {
teamIds []int
}
func (s *GenericOAuth) Type() int {
func (s *SocialGenericOAuth) Type() int {
return int(models.GENERIC)
}
func (s *GenericOAuth) IsEmailAllowed(email string) bool {
func (s *SocialGenericOAuth) IsEmailAllowed(email string) bool {
return isEmailAllowed(email, s.allowedDomains)
}
func (s *GenericOAuth) IsSignupAllowed() bool {
func (s *SocialGenericOAuth) IsSignupAllowed() bool {
return s.allowSignup
}
func (s *GenericOAuth) IsTeamMember(client *http.Client) bool {
func (s *SocialGenericOAuth) IsTeamMember(client *http.Client) bool {
if len(s.teamIds) == 0 {
return true
}
@@ -53,7 +56,7 @@ func (s *GenericOAuth) IsTeamMember(client *http.Client) bool {
return false
}
func (s *GenericOAuth) IsOrganizationMember(client *http.Client) bool {
func (s *SocialGenericOAuth) IsOrganizationMember(client *http.Client) bool {
if len(s.allowedOrganizations) == 0 {
return true
}
@@ -74,7 +77,7 @@ func (s *GenericOAuth) IsOrganizationMember(client *http.Client) bool {
return false
}
func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
func (s *SocialGenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
type Record struct {
Email string `json:"email"`
Primary bool `json:"primary"`
@@ -115,7 +118,7 @@ func (s *GenericOAuth) FetchPrivateEmail(client *http.Client) (string, error) {
return email, nil
}
func (s *GenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error) {
func (s *SocialGenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error) {
type Record struct {
Id int `json:"id"`
}
@@ -140,7 +143,7 @@ func (s *GenericOAuth) FetchTeamMemberships(client *http.Client) ([]int, error)
return ids, nil
}
func (s *GenericOAuth) FetchOrganizations(client *http.Client) ([]string, error) {
func (s *SocialGenericOAuth) FetchOrganizations(client *http.Client) ([]string, error) {
type Record struct {
Login string `json:"login"`
}
@@ -165,53 +168,50 @@ func (s *GenericOAuth) FetchOrganizations(client *http.Client) ([]string, error)
return logins, nil
}
func (s *GenericOAuth) UserInfo(client *http.Client) (*BasicUserInfo, error) {
var data struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Login string `json:"login"`
Username string `json:"username"`
Email string `json:"email"`
Attributes map[string][]string `json:"attributes"`
}
type UserInfoJson struct {
Name string `json:"name"`
DisplayName string `json:"display_name"`
Login string `json:"login"`
Username string `json:"username"`
Email string `json:"email"`
Upn string `json:"upn"`
Attributes map[string][]string `json:"attributes"`
}
response, err := HttpGet(client, s.apiUrl)
if err != nil {
return nil, fmt.Errorf("Error getting user info: %s", err)
}
func (s *SocialGenericOAuth) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
var data UserInfoJson
err = json.Unmarshal(response.Body, &data)
if err != nil {
return nil, fmt.Errorf("Error getting user info: %s", err)
}
userInfo := &BasicUserInfo{
Name: data.Name,
Login: data.Login,
Email: data.Email,
}
if userInfo.Email == "" && data.Attributes["email:primary"] != nil {
userInfo.Email = data.Attributes["email:primary"][0]
}
if userInfo.Email == "" {
userInfo.Email, err = s.FetchPrivateEmail(client)
if s.extractToken(&data, token) != true {
response, err := HttpGet(client, s.apiUrl)
if err != nil {
return nil, err
return nil, fmt.Errorf("Error getting user info: %s", err)
}
err = json.Unmarshal(response.Body, &data)
if err != nil {
return nil, fmt.Errorf("Error decoding user info JSON: %s", err)
}
}
if userInfo.Name == "" && data.DisplayName != "" {
userInfo.Name = data.DisplayName
name, err := s.extractName(data)
if err != nil {
return nil, err
}
if userInfo.Login == "" && data.Username != "" {
userInfo.Login = data.Username
email, err := s.extractEmail(data, client)
if err != nil {
return nil, err
}
if userInfo.Login == "" {
userInfo.Login = data.Email
login, err := s.extractLogin(data, email)
if err != nil {
return nil, err
}
userInfo := &BasicUserInfo{
Name: name,
Login: login,
Email: email,
}
if !s.IsTeamMember(client) {
@@ -224,3 +224,76 @@ func (s *GenericOAuth) UserInfo(client *http.Client) (*BasicUserInfo, error) {
return userInfo, nil
}
func (s *SocialGenericOAuth) extractToken(data *UserInfoJson, token *oauth2.Token) bool {
idToken := token.Extra("id_token")
if idToken == nil {
s.log.Debug("No id_token found", "token", token)
return false
}
jwtRegexp := regexp.MustCompile("^([-_a-zA-Z0-9]+)[.]([-_a-zA-Z0-9]+)[.]([-_a-zA-Z0-9]+)$")
matched := jwtRegexp.FindStringSubmatch(idToken.(string))
if matched == nil {
s.log.Debug("id_token is not in JWT format", "id_token", idToken.(string))
return false
}
payload, err := base64.RawURLEncoding.DecodeString(matched[2])
if err != nil {
s.log.Error("Error base64 decoding id_token", "raw_payload", matched[2], "err", err)
return false
}
err = json.Unmarshal(payload, data)
if err != nil {
s.log.Error("Error decoding id_token JSON", "payload", string(payload), "err", err)
return false
}
s.log.Debug("Received id_token", "json", string(payload), "data", data)
return true
}
func (s *SocialGenericOAuth) extractEmail(data UserInfoJson, client *http.Client) (string, error) {
if data.Email != "" {
return data.Email, nil
}
if data.Attributes["email:primary"] != nil {
return data.Attributes["email:primary"][0], nil
}
if data.Upn != "" {
emailAddr, emailErr := mail.ParseAddress(data.Upn)
if emailErr == nil {
return emailAddr.Address, nil
}
}
return s.FetchPrivateEmail(client)
}
func (s *SocialGenericOAuth) extractLogin(data UserInfoJson, email string) (string, error) {
if data.Login != "" {
return data.Login, nil
}
if data.Username != "" {
return data.Username, nil
}
return email, nil
}
func (s *SocialGenericOAuth) extractName(data UserInfoJson) (string, error) {
if data.Name != "" {
return data.Name, nil
}
if data.DisplayName != "" {
return data.DisplayName, nil
}
return "", nil
}
+2 -2
View File
@@ -12,7 +12,7 @@ import (
)
type SocialGithub struct {
*oauth2.Config
*SocialBase
allowedDomains []string
allowedOrganizations []string
apiUrl string
@@ -192,7 +192,7 @@ func (s *SocialGithub) FetchOrganizations(client *http.Client, organizationsUrl
return logins, nil
}
func (s *SocialGithub) UserInfo(client *http.Client) (*BasicUserInfo, error) {
func (s *SocialGithub) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
var data struct {
Id int `json:"id"`
+2 -2
View File
@@ -11,7 +11,7 @@ import (
)
type SocialGoogle struct {
*oauth2.Config
*SocialBase
allowedDomains []string
hostedDomain string
apiUrl string
@@ -30,7 +30,7 @@ func (s *SocialGoogle) IsSignupAllowed() bool {
return s.allowSignup
}
func (s *SocialGoogle) UserInfo(client *http.Client) (*BasicUserInfo, error) {
func (s *SocialGoogle) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
var data struct {
Name string `json:"name"`
Email string `json:"email"`
+2 -2
View File
@@ -11,7 +11,7 @@ import (
)
type SocialGrafanaCom struct {
*oauth2.Config
*SocialBase
url string
allowedOrganizations []string
allowSignup bool
@@ -49,7 +49,7 @@ func (s *SocialGrafanaCom) IsOrganizationMember(organizations []OrgRecord) bool
return false
}
func (s *SocialGrafanaCom) UserInfo(client *http.Client) (*BasicUserInfo, error) {
func (s *SocialGrafanaCom) UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error) {
var data struct {
Name string `json:"name"`
Login string `json:"username"`
+28 -7
View File
@@ -4,9 +4,11 @@ import (
"net/http"
"strings"
"golang.org/x/net/context"
"context"
"golang.org/x/oauth2"
"github.com/grafana/grafana/pkg/log"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/util"
)
@@ -21,7 +23,7 @@ type BasicUserInfo struct {
type SocialConnector interface {
Type() int
UserInfo(client *http.Client) (*BasicUserInfo, error)
UserInfo(client *http.Client, token *oauth2.Token) (*BasicUserInfo, error)
IsEmailAllowed(email string) bool
IsSignupAllowed() bool
@@ -30,6 +32,11 @@ type SocialConnector interface {
Client(ctx context.Context, t *oauth2.Token) *http.Client
}
type SocialBase struct {
*oauth2.Config
log log.Logger
}
type Error struct {
s string
}
@@ -90,10 +97,15 @@ func NewOAuthService() {
Scopes: info.Scopes,
}
logger := log.New("oauth." + name)
// GitHub.
if name == "github" {
SocialMap["github"] = &SocialGithub{
Config: &config,
SocialBase: &SocialBase{
Config: &config,
log: logger,
},
allowedDomains: info.AllowedDomains,
apiUrl: info.ApiUrl,
allowSignup: info.AllowSignup,
@@ -105,7 +117,10 @@ func NewOAuthService() {
// Google.
if name == "google" {
SocialMap["google"] = &SocialGoogle{
Config: &config,
SocialBase: &SocialBase{
Config: &config,
log: logger,
},
allowedDomains: info.AllowedDomains,
hostedDomain: info.HostedDomain,
apiUrl: info.ApiUrl,
@@ -115,8 +130,11 @@ func NewOAuthService() {
// Generic - Uses the same scheme as Github.
if name == "generic_oauth" {
SocialMap["generic_oauth"] = &GenericOAuth{
Config: &config,
SocialMap["generic_oauth"] = &SocialGenericOAuth{
SocialBase: &SocialBase{
Config: &config,
log: logger,
},
allowedDomains: info.AllowedDomains,
apiUrl: info.ApiUrl,
allowSignup: info.AllowSignup,
@@ -138,7 +156,10 @@ func NewOAuthService() {
}
SocialMap["grafana_com"] = &SocialGrafanaCom{
Config: &config,
SocialBase: &SocialBase{
Config: &config,
log: logger,
},
url: setting.GrafanaComUrl,
allowSignup: info.AllowSignup,
allowedOrganizations: util.SplitString(sec.Key("allowed_organizations").String()),
+50 -16
View File
@@ -3,6 +3,7 @@ package cloudwatch
import (
"context"
"errors"
"fmt"
"reflect"
"sort"
"strings"
@@ -187,18 +188,6 @@ func (e *CloudWatchExecutor) executeMetricFindQuery(ctx context.Context, queryCo
data, err = e.handleGetEbsVolumeIds(ctx, parameters, queryContext)
break
case "ec2_instance_attribute":
region := parameters.Get("region").MustString()
dsInfo := e.getDsInfo(region)
cfg, err := e.getAwsConfig(dsInfo)
if err != nil {
return nil, errors.New("Failed to call ec2:DescribeInstances")
}
sess, err := session.NewSession(cfg)
if err != nil {
return nil, errors.New("Failed to call ec2:DescribeInstances")
}
e.ec2Svc = ec2.New(sess, cfg)
data, err = e.handleGetEc2InstanceAttribute(ctx, parameters, queryContext)
break
}
@@ -226,6 +215,21 @@ func transformToTable(data []suggestData, result *tsdb.QueryResult) {
result.Meta.Set("rowCount", len(data))
}
func parseMultiSelectValue(input string) []string {
trimmedInput := strings.TrimSpace(input)
if strings.HasPrefix(trimmedInput, "{") {
values := strings.Split(strings.TrimRight(strings.TrimLeft(trimmedInput, "{"), "}"), ",")
trimValues := make([]string, len(values))
for i, v := range values {
trimValues[i] = strings.TrimSpace(v)
}
return trimValues
} else {
return []string{trimmedInput}
}
}
// Whenever this list is updated, frontend list should also be updated.
// Please update the region list in public/app/plugins/datasource/cloudwatch/partials/config.html
func (e *CloudWatchExecutor) handleGetRegions(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
@@ -364,19 +368,44 @@ func (e *CloudWatchExecutor) handleGetDimensionValues(ctx context.Context, param
return result, nil
}
func (e *CloudWatchExecutor) ensureClientSession(region string) error {
if e.ec2Svc == nil {
dsInfo := e.getDsInfo(region)
cfg, err := e.getAwsConfig(dsInfo)
if err != nil {
return fmt.Errorf("Failed to call ec2:getAwsConfig, %v", err)
}
sess, err := session.NewSession(cfg)
if err != nil {
return fmt.Errorf("Failed to call ec2:NewSession, %v", err)
}
e.ec2Svc = ec2.New(sess, cfg)
}
return nil
}
func (e *CloudWatchExecutor) handleGetEbsVolumeIds(ctx context.Context, parameters *simplejson.Json, queryContext *tsdb.TsdbQuery) ([]suggestData, error) {
region := parameters.Get("region").MustString()
instanceId := parameters.Get("instanceId").MustString()
instanceIds := []*string{aws.String(instanceId)}
err := e.ensureClientSession(region)
if err != nil {
return nil, err
}
instanceIds := aws.StringSlice(parseMultiSelectValue(instanceId))
instances, err := e.ec2DescribeInstances(region, nil, instanceIds)
if err != nil {
return nil, err
}
result := make([]suggestData, 0)
for _, mapping := range instances.Reservations[0].Instances[0].BlockDeviceMappings {
result = append(result, suggestData{Text: *mapping.Ebs.VolumeId, Value: *mapping.Ebs.VolumeId})
for _, reservation := range instances.Reservations {
for _, instance := range reservation.Instances {
for _, mapping := range instance.BlockDeviceMappings {
result = append(result, suggestData{Text: *mapping.Ebs.VolumeId, Value: *mapping.Ebs.VolumeId})
}
}
}
return result, nil
@@ -403,6 +432,11 @@ func (e *CloudWatchExecutor) handleGetEc2InstanceAttribute(ctx context.Context,
}
}
err := e.ensureClientSession(region)
if err != nil {
return nil, err
}
instances, err := e.ec2DescribeInstances(region, filters, nil)
if err != nil {
return nil, err
@@ -478,7 +512,7 @@ func (e *CloudWatchExecutor) cloudwatchListMetrics(region string, namespace stri
return !lastPage
})
if err != nil {
return nil, errors.New("Failed to call cloudwatch:ListMetrics")
return nil, fmt.Errorf("Failed to call cloudwatch:ListMetrics, %v", err)
}
return &resp, nil
@@ -8,6 +8,7 @@ import (
"github.com/aws/aws-sdk-go/service/cloudwatch"
"github.com/aws/aws-sdk-go/service/ec2"
"github.com/aws/aws-sdk-go/service/ec2/ec2iface"
"github.com/bmizerany/assert"
"github.com/grafana/grafana/pkg/components/simplejson"
"github.com/grafana/grafana/pkg/tsdb"
. "github.com/smartystreets/goconvey/convey"
@@ -114,4 +115,85 @@ func TestCloudWatchMetrics(t *testing.T) {
So(result[0].Text, ShouldEqual, "i-12345678")
})
})
Convey("When calling handleGetEbsVolumeIds", t, func() {
executor := &CloudWatchExecutor{
ec2Svc: mockedEc2{Resp: ec2.DescribeInstancesOutput{
Reservations: []*ec2.Reservation{
{
Instances: []*ec2.Instance{
{
InstanceId: aws.String("i-1"),
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-1-1")}},
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-1-2")}},
},
},
{
InstanceId: aws.String("i-2"),
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-2-1")}},
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-2-2")}},
},
},
},
},
{
Instances: []*ec2.Instance{
{
InstanceId: aws.String("i-3"),
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-3-1")}},
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-3-2")}},
},
},
{
InstanceId: aws.String("i-4"),
BlockDeviceMappings: []*ec2.InstanceBlockDeviceMapping{
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-4-1")}},
{Ebs: &ec2.EbsInstanceBlockDevice{VolumeId: aws.String("vol-4-2")}},
},
},
},
},
},
}},
}
json := simplejson.New()
json.Set("region", "us-east-1")
json.Set("instanceId", "{i-1, i-2, i-3, i-4}")
result, _ := executor.handleGetEbsVolumeIds(context.Background(), json, &tsdb.TsdbQuery{})
Convey("Should return all 8 VolumeIds", func() {
So(len(result), ShouldEqual, 8)
So(result[0].Text, ShouldEqual, "vol-1-1")
So(result[1].Text, ShouldEqual, "vol-1-2")
So(result[2].Text, ShouldEqual, "vol-2-1")
So(result[3].Text, ShouldEqual, "vol-2-2")
So(result[4].Text, ShouldEqual, "vol-3-1")
So(result[5].Text, ShouldEqual, "vol-3-2")
So(result[6].Text, ShouldEqual, "vol-4-1")
So(result[7].Text, ShouldEqual, "vol-4-2")
})
})
}
func TestParseMultiSelectValue(t *testing.T) {
var values []string
values = parseMultiSelectValue(" i-someInstance ")
assert.Equal(t, []string{"i-someInstance"}, values)
values = parseMultiSelectValue("{i-05}")
assert.Equal(t, []string{"i-05"}, values)
values = parseMultiSelectValue(" {i-01, i-03, i-04} ")
assert.Equal(t, []string{"i-01", "i-03", "i-04"}, values)
values = parseMultiSelectValue("i-{01}")
assert.Equal(t, []string{"i-{01}"}, values)
}
-636
View File
@@ -1,636 +0,0 @@
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: tsdb_plugin.proto
/*
Package proto is a generated protocol buffer package.
It is generated from these files:
tsdb_plugin.proto
It has these top-level messages:
TsdbQuery
Query
TimeRange
Response
QueryResult
Table
TableColumn
TableRow
RowValue
DatasourceInfo
TimeSeries
Point
*/
package proto
import proto1 "github.com/golang/protobuf/proto"
import fmt "fmt"
import math "math"
import (
context "golang.org/x/net/context"
grpc "google.golang.org/grpc"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto1.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto1.ProtoPackageIsVersion2 // please upgrade the proto package
type RowValue_Kind int32
const (
// Field type null.
RowValue_TYPE_NULL RowValue_Kind = 0
// Field type double.
RowValue_TYPE_DOUBLE RowValue_Kind = 1
// Field type int64.
RowValue_TYPE_INT64 RowValue_Kind = 2
// Field type bool.
RowValue_TYPE_BOOL RowValue_Kind = 3
// Field type string.
RowValue_TYPE_STRING RowValue_Kind = 4
// Field type bytes.
RowValue_TYPE_BYTES RowValue_Kind = 5
)
var RowValue_Kind_name = map[int32]string{
0: "TYPE_NULL",
1: "TYPE_DOUBLE",
2: "TYPE_INT64",
3: "TYPE_BOOL",
4: "TYPE_STRING",
5: "TYPE_BYTES",
}
var RowValue_Kind_value = map[string]int32{
"TYPE_NULL": 0,
"TYPE_DOUBLE": 1,
"TYPE_INT64": 2,
"TYPE_BOOL": 3,
"TYPE_STRING": 4,
"TYPE_BYTES": 5,
}
func (x RowValue_Kind) String() string {
return proto1.EnumName(RowValue_Kind_name, int32(x))
}
func (RowValue_Kind) EnumDescriptor() ([]byte, []int) { return fileDescriptor0, []int{8, 0} }
type TsdbQuery struct {
TimeRange *TimeRange `protobuf:"bytes,1,opt,name=timeRange" json:"timeRange,omitempty"`
Datasource *DatasourceInfo `protobuf:"bytes,2,opt,name=datasource" json:"datasource,omitempty"`
Queries []*Query `protobuf:"bytes,3,rep,name=queries" json:"queries,omitempty"`
}
func (m *TsdbQuery) Reset() { *m = TsdbQuery{} }
func (m *TsdbQuery) String() string { return proto1.CompactTextString(m) }
func (*TsdbQuery) ProtoMessage() {}
func (*TsdbQuery) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{0} }
func (m *TsdbQuery) GetTimeRange() *TimeRange {
if m != nil {
return m.TimeRange
}
return nil
}
func (m *TsdbQuery) GetDatasource() *DatasourceInfo {
if m != nil {
return m.Datasource
}
return nil
}
func (m *TsdbQuery) GetQueries() []*Query {
if m != nil {
return m.Queries
}
return nil
}
type Query struct {
RefId string `protobuf:"bytes,1,opt,name=refId" json:"refId,omitempty"`
MaxDataPoints int64 `protobuf:"varint,2,opt,name=maxDataPoints" json:"maxDataPoints,omitempty"`
IntervalMs int64 `protobuf:"varint,3,opt,name=intervalMs" json:"intervalMs,omitempty"`
ModelJson string `protobuf:"bytes,4,opt,name=modelJson" json:"modelJson,omitempty"`
}
func (m *Query) Reset() { *m = Query{} }
func (m *Query) String() string { return proto1.CompactTextString(m) }
func (*Query) ProtoMessage() {}
func (*Query) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{1} }
func (m *Query) GetRefId() string {
if m != nil {
return m.RefId
}
return ""
}
func (m *Query) GetMaxDataPoints() int64 {
if m != nil {
return m.MaxDataPoints
}
return 0
}
func (m *Query) GetIntervalMs() int64 {
if m != nil {
return m.IntervalMs
}
return 0
}
func (m *Query) GetModelJson() string {
if m != nil {
return m.ModelJson
}
return ""
}
type TimeRange struct {
FromRaw string `protobuf:"bytes,1,opt,name=fromRaw" json:"fromRaw,omitempty"`
ToRaw string `protobuf:"bytes,2,opt,name=toRaw" json:"toRaw,omitempty"`
FromEpochMs int64 `protobuf:"varint,3,opt,name=fromEpochMs" json:"fromEpochMs,omitempty"`
ToEpochMs int64 `protobuf:"varint,4,opt,name=toEpochMs" json:"toEpochMs,omitempty"`
}
func (m *TimeRange) Reset() { *m = TimeRange{} }
func (m *TimeRange) String() string { return proto1.CompactTextString(m) }
func (*TimeRange) ProtoMessage() {}
func (*TimeRange) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{2} }
func (m *TimeRange) GetFromRaw() string {
if m != nil {
return m.FromRaw
}
return ""
}
func (m *TimeRange) GetToRaw() string {
if m != nil {
return m.ToRaw
}
return ""
}
func (m *TimeRange) GetFromEpochMs() int64 {
if m != nil {
return m.FromEpochMs
}
return 0
}
func (m *TimeRange) GetToEpochMs() int64 {
if m != nil {
return m.ToEpochMs
}
return 0
}
type Response struct {
Results []*QueryResult `protobuf:"bytes,1,rep,name=results" json:"results,omitempty"`
}
func (m *Response) Reset() { *m = Response{} }
func (m *Response) String() string { return proto1.CompactTextString(m) }
func (*Response) ProtoMessage() {}
func (*Response) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{3} }
func (m *Response) GetResults() []*QueryResult {
if m != nil {
return m.Results
}
return nil
}
type QueryResult struct {
Error string `protobuf:"bytes,1,opt,name=error" json:"error,omitempty"`
RefId string `protobuf:"bytes,2,opt,name=refId" json:"refId,omitempty"`
MetaJson string `protobuf:"bytes,3,opt,name=metaJson" json:"metaJson,omitempty"`
Series []*TimeSeries `protobuf:"bytes,4,rep,name=series" json:"series,omitempty"`
Tables []*Table `protobuf:"bytes,5,rep,name=tables" json:"tables,omitempty"`
}
func (m *QueryResult) Reset() { *m = QueryResult{} }
func (m *QueryResult) String() string { return proto1.CompactTextString(m) }
func (*QueryResult) ProtoMessage() {}
func (*QueryResult) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{4} }
func (m *QueryResult) GetError() string {
if m != nil {
return m.Error
}
return ""
}
func (m *QueryResult) GetRefId() string {
if m != nil {
return m.RefId
}
return ""
}
func (m *QueryResult) GetMetaJson() string {
if m != nil {
return m.MetaJson
}
return ""
}
func (m *QueryResult) GetSeries() []*TimeSeries {
if m != nil {
return m.Series
}
return nil
}
func (m *QueryResult) GetTables() []*Table {
if m != nil {
return m.Tables
}
return nil
}
type Table struct {
Columns []*TableColumn `protobuf:"bytes,1,rep,name=columns" json:"columns,omitempty"`
Rows []*TableRow `protobuf:"bytes,2,rep,name=rows" json:"rows,omitempty"`
}
func (m *Table) Reset() { *m = Table{} }
func (m *Table) String() string { return proto1.CompactTextString(m) }
func (*Table) ProtoMessage() {}
func (*Table) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{5} }
func (m *Table) GetColumns() []*TableColumn {
if m != nil {
return m.Columns
}
return nil
}
func (m *Table) GetRows() []*TableRow {
if m != nil {
return m.Rows
}
return nil
}
type TableColumn struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
}
func (m *TableColumn) Reset() { *m = TableColumn{} }
func (m *TableColumn) String() string { return proto1.CompactTextString(m) }
func (*TableColumn) ProtoMessage() {}
func (*TableColumn) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{6} }
func (m *TableColumn) GetName() string {
if m != nil {
return m.Name
}
return ""
}
type TableRow struct {
Values []*RowValue `protobuf:"bytes,1,rep,name=values" json:"values,omitempty"`
}
func (m *TableRow) Reset() { *m = TableRow{} }
func (m *TableRow) String() string { return proto1.CompactTextString(m) }
func (*TableRow) ProtoMessage() {}
func (*TableRow) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{7} }
func (m *TableRow) GetValues() []*RowValue {
if m != nil {
return m.Values
}
return nil
}
type RowValue struct {
Kind RowValue_Kind `protobuf:"varint,1,opt,name=kind,enum=plugins.RowValue_Kind" json:"kind,omitempty"`
DoubleValue float64 `protobuf:"fixed64,2,opt,name=doubleValue" json:"doubleValue,omitempty"`
Int64Value int64 `protobuf:"varint,3,opt,name=int64Value" json:"int64Value,omitempty"`
BoolValue bool `protobuf:"varint,4,opt,name=boolValue" json:"boolValue,omitempty"`
StringValue string `protobuf:"bytes,5,opt,name=stringValue" json:"stringValue,omitempty"`
BytesValue []byte `protobuf:"bytes,6,opt,name=bytesValue,proto3" json:"bytesValue,omitempty"`
}
func (m *RowValue) Reset() { *m = RowValue{} }
func (m *RowValue) String() string { return proto1.CompactTextString(m) }
func (*RowValue) ProtoMessage() {}
func (*RowValue) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{8} }
func (m *RowValue) GetKind() RowValue_Kind {
if m != nil {
return m.Kind
}
return RowValue_TYPE_NULL
}
func (m *RowValue) GetDoubleValue() float64 {
if m != nil {
return m.DoubleValue
}
return 0
}
func (m *RowValue) GetInt64Value() int64 {
if m != nil {
return m.Int64Value
}
return 0
}
func (m *RowValue) GetBoolValue() bool {
if m != nil {
return m.BoolValue
}
return false
}
func (m *RowValue) GetStringValue() string {
if m != nil {
return m.StringValue
}
return ""
}
func (m *RowValue) GetBytesValue() []byte {
if m != nil {
return m.BytesValue
}
return nil
}
type DatasourceInfo struct {
Id int64 `protobuf:"varint,1,opt,name=id" json:"id,omitempty"`
OrgId int64 `protobuf:"varint,2,opt,name=orgId" json:"orgId,omitempty"`
Name string `protobuf:"bytes,3,opt,name=name" json:"name,omitempty"`
Type string `protobuf:"bytes,4,opt,name=type" json:"type,omitempty"`
Url string `protobuf:"bytes,5,opt,name=url" json:"url,omitempty"`
JsonData string `protobuf:"bytes,6,opt,name=jsonData" json:"jsonData,omitempty"`
SecureJsonData string `protobuf:"bytes,7,opt,name=secureJsonData" json:"secureJsonData,omitempty"`
}
func (m *DatasourceInfo) Reset() { *m = DatasourceInfo{} }
func (m *DatasourceInfo) String() string { return proto1.CompactTextString(m) }
func (*DatasourceInfo) ProtoMessage() {}
func (*DatasourceInfo) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{9} }
func (m *DatasourceInfo) GetId() int64 {
if m != nil {
return m.Id
}
return 0
}
func (m *DatasourceInfo) GetOrgId() int64 {
if m != nil {
return m.OrgId
}
return 0
}
func (m *DatasourceInfo) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *DatasourceInfo) GetType() string {
if m != nil {
return m.Type
}
return ""
}
func (m *DatasourceInfo) GetUrl() string {
if m != nil {
return m.Url
}
return ""
}
func (m *DatasourceInfo) GetJsonData() string {
if m != nil {
return m.JsonData
}
return ""
}
func (m *DatasourceInfo) GetSecureJsonData() string {
if m != nil {
return m.SecureJsonData
}
return ""
}
type TimeSeries struct {
Name string `protobuf:"bytes,1,opt,name=name" json:"name,omitempty"`
Tags map[string]string `protobuf:"bytes,2,rep,name=tags" json:"tags,omitempty" protobuf_key:"bytes,1,opt,name=key" protobuf_val:"bytes,2,opt,name=value"`
Points []*Point `protobuf:"bytes,3,rep,name=points" json:"points,omitempty"`
}
func (m *TimeSeries) Reset() { *m = TimeSeries{} }
func (m *TimeSeries) String() string { return proto1.CompactTextString(m) }
func (*TimeSeries) ProtoMessage() {}
func (*TimeSeries) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{10} }
func (m *TimeSeries) GetName() string {
if m != nil {
return m.Name
}
return ""
}
func (m *TimeSeries) GetTags() map[string]string {
if m != nil {
return m.Tags
}
return nil
}
func (m *TimeSeries) GetPoints() []*Point {
if m != nil {
return m.Points
}
return nil
}
type Point struct {
Timestamp int64 `protobuf:"varint,1,opt,name=timestamp" json:"timestamp,omitempty"`
Value float64 `protobuf:"fixed64,2,opt,name=value" json:"value,omitempty"`
}
func (m *Point) Reset() { *m = Point{} }
func (m *Point) String() string { return proto1.CompactTextString(m) }
func (*Point) ProtoMessage() {}
func (*Point) Descriptor() ([]byte, []int) { return fileDescriptor0, []int{11} }
func (m *Point) GetTimestamp() int64 {
if m != nil {
return m.Timestamp
}
return 0
}
func (m *Point) GetValue() float64 {
if m != nil {
return m.Value
}
return 0
}
func init() {
proto1.RegisterType((*TsdbQuery)(nil), "plugins.TsdbQuery")
proto1.RegisterType((*Query)(nil), "plugins.Query")
proto1.RegisterType((*TimeRange)(nil), "plugins.TimeRange")
proto1.RegisterType((*Response)(nil), "plugins.Response")
proto1.RegisterType((*QueryResult)(nil), "plugins.QueryResult")
proto1.RegisterType((*Table)(nil), "plugins.Table")
proto1.RegisterType((*TableColumn)(nil), "plugins.TableColumn")
proto1.RegisterType((*TableRow)(nil), "plugins.TableRow")
proto1.RegisterType((*RowValue)(nil), "plugins.RowValue")
proto1.RegisterType((*DatasourceInfo)(nil), "plugins.DatasourceInfo")
proto1.RegisterType((*TimeSeries)(nil), "plugins.TimeSeries")
proto1.RegisterType((*Point)(nil), "plugins.Point")
proto1.RegisterEnum("plugins.RowValue_Kind", RowValue_Kind_name, RowValue_Kind_value)
}
// Reference imports to suppress errors if they are not otherwise used.
var _ context.Context
var _ grpc.ClientConn
// This is a compile-time assertion to ensure that this generated file
// is compatible with the grpc package it is being compiled against.
const _ = grpc.SupportPackageIsVersion4
// Client API for TsdbPlugin service
type TsdbPluginClient interface {
Query(ctx context.Context, in *TsdbQuery, opts ...grpc.CallOption) (*Response, error)
}
type tsdbPluginClient struct {
cc *grpc.ClientConn
}
func NewTsdbPluginClient(cc *grpc.ClientConn) TsdbPluginClient {
return &tsdbPluginClient{cc}
}
func (c *tsdbPluginClient) Query(ctx context.Context, in *TsdbQuery, opts ...grpc.CallOption) (*Response, error) {
out := new(Response)
err := grpc.Invoke(ctx, "/plugins.TsdbPlugin/Query", in, out, c.cc, opts...)
if err != nil {
return nil, err
}
return out, nil
}
// Server API for TsdbPlugin service
type TsdbPluginServer interface {
Query(context.Context, *TsdbQuery) (*Response, error)
}
func RegisterTsdbPluginServer(s *grpc.Server, srv TsdbPluginServer) {
s.RegisterService(&_TsdbPlugin_serviceDesc, srv)
}
func _TsdbPlugin_Query_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
in := new(TsdbQuery)
if err := dec(in); err != nil {
return nil, err
}
if interceptor == nil {
return srv.(TsdbPluginServer).Query(ctx, in)
}
info := &grpc.UnaryServerInfo{
Server: srv,
FullMethod: "/plugins.TsdbPlugin/Query",
}
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
return srv.(TsdbPluginServer).Query(ctx, req.(*TsdbQuery))
}
return interceptor(ctx, in, info, handler)
}
var _TsdbPlugin_serviceDesc = grpc.ServiceDesc{
ServiceName: "plugins.TsdbPlugin",
HandlerType: (*TsdbPluginServer)(nil),
Methods: []grpc.MethodDesc{
{
MethodName: "Query",
Handler: _TsdbPlugin_Query_Handler,
},
},
Streams: []grpc.StreamDesc{},
Metadata: "tsdb_plugin.proto",
}
func init() { proto1.RegisterFile("tsdb_plugin.proto", fileDescriptor0) }
var fileDescriptor0 = []byte{
// 811 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x55, 0x5d, 0x8f, 0x1b, 0x35,
0x14, 0x65, 0xbe, 0x92, 0xcc, 0x0d, 0x0d, 0xa9, 0xa9, 0x20, 0x5a, 0x01, 0x5a, 0x46, 0x50, 0x05,
0x90, 0x22, 0x08, 0xa5, 0x45, 0x85, 0xa7, 0xd0, 0x08, 0xa5, 0x84, 0xdd, 0xc5, 0x3b, 0x45, 0x2a,
0x0f, 0x54, 0x93, 0x8c, 0x13, 0x86, 0xce, 0x8c, 0x83, 0xed, 0xd9, 0x10, 0xf1, 0xc4, 0x3f, 0xe1,
0x99, 0x67, 0x7e, 0x00, 0x3f, 0xad, 0xf2, 0x1d, 0xcf, 0x47, 0xd2, 0x7d, 0x9a, 0xb9, 0xe7, 0x1c,
0x5f, 0x5f, 0x5f, 0x1f, 0xdb, 0x70, 0x57, 0xc9, 0x78, 0xf5, 0x62, 0x97, 0x16, 0xdb, 0x24, 0x9f,
0xec, 0x04, 0x57, 0x9c, 0x74, 0xcb, 0x48, 0x06, 0xff, 0x58, 0xe0, 0x87, 0x32, 0x5e, 0xfd, 0x54,
0x30, 0x71, 0x20, 0x9f, 0x83, 0xaf, 0x92, 0x8c, 0xd1, 0x28, 0xdf, 0xb2, 0x91, 0x75, 0x6e, 0x8d,
0xfb, 0x53, 0x32, 0x31, 0xd2, 0x49, 0x58, 0x31, 0xb4, 0x11, 0x91, 0x47, 0x00, 0x71, 0xa4, 0x22,
0xc9, 0x0b, 0xb1, 0x66, 0x23, 0x1b, 0x87, 0xbc, 0x5b, 0x0f, 0x79, 0x52, 0x53, 0x8b, 0x7c, 0xc3,
0x69, 0x4b, 0x4a, 0xc6, 0xd0, 0xfd, 0xa3, 0x60, 0x22, 0x61, 0x72, 0xe4, 0x9c, 0x3b, 0xe3, 0xfe,
0x74, 0x50, 0x8f, 0xc2, 0x5a, 0x68, 0x45, 0x07, 0x7f, 0x5b, 0xe0, 0x95, 0xe5, 0xdd, 0x03, 0x4f,
0xb0, 0xcd, 0x22, 0xc6, 0xd2, 0x7c, 0x5a, 0x06, 0xe4, 0x23, 0xb8, 0x93, 0x45, 0x7f, 0xea, 0xa9,
0xae, 0x78, 0x92, 0x2b, 0x89, 0x55, 0x38, 0xf4, 0x18, 0x24, 0x1f, 0x00, 0x24, 0xb9, 0x62, 0xe2,
0x26, 0x4a, 0x7f, 0xd4, 0x53, 0x6a, 0x49, 0x0b, 0x21, 0xef, 0x81, 0x9f, 0xf1, 0x98, 0xa5, 0x4f,
0x25, 0xcf, 0x47, 0x2e, 0xe6, 0x6f, 0x80, 0xe0, 0x2f, 0xf0, 0xeb, 0xe5, 0x93, 0x11, 0x74, 0x37,
0x82, 0x67, 0x34, 0xda, 0x9b, 0x42, 0xaa, 0x50, 0x17, 0xa8, 0xb8, 0xc6, 0xed, 0xb2, 0x40, 0x0c,
0xc8, 0x39, 0xf4, 0xb5, 0x60, 0xbe, 0xe3, 0xeb, 0xdf, 0xea, 0xb9, 0xdb, 0x90, 0x9e, 0x5c, 0xf1,
0x8a, 0x77, 0x91, 0x6f, 0x80, 0xe0, 0x31, 0xf4, 0x28, 0x93, 0x3b, 0x9e, 0x4b, 0x46, 0x26, 0xd0,
0x15, 0x4c, 0x16, 0xa9, 0x92, 0x23, 0x0b, 0xdb, 0x76, 0xef, 0xa4, 0x6d, 0x48, 0xd2, 0x4a, 0x14,
0xfc, 0x6b, 0x41, 0xbf, 0x45, 0xe8, 0x0a, 0x99, 0x10, 0x5c, 0x54, 0x2d, 0xc4, 0xa0, 0x69, 0xac,
0xdd, 0x6e, 0xec, 0x19, 0xf4, 0x32, 0xa6, 0x22, 0xec, 0x88, 0x83, 0x44, 0x1d, 0x93, 0xcf, 0xa0,
0x23, 0xcb, 0xdd, 0x73, 0xb1, 0x8c, 0xb7, 0x8f, 0x6c, 0x72, 0x8d, 0x14, 0x35, 0x12, 0x72, 0x1f,
0x3a, 0x2a, 0x5a, 0xa5, 0x4c, 0x8e, 0xbc, 0x93, 0xad, 0x0e, 0x35, 0x4c, 0x0d, 0x1b, 0xfc, 0x0a,
0x1e, 0x02, 0x7a, 0x95, 0x6b, 0x9e, 0x16, 0x59, 0xfe, 0xfa, 0x2a, 0x51, 0xf0, 0x1d, 0x92, 0xb4,
0x12, 0x91, 0x8f, 0xc1, 0x15, 0x7c, 0xaf, 0x77, 0x5e, 0x8b, 0xef, 0x9e, 0xa4, 0xe7, 0x7b, 0x8a,
0x74, 0xf0, 0x21, 0xf4, 0x5b, 0xc3, 0x09, 0x01, 0x37, 0x8f, 0x32, 0x66, 0x5a, 0x81, 0xff, 0xc1,
0x57, 0xd0, 0xab, 0x06, 0x91, 0x4f, 0xa0, 0x73, 0x13, 0xa5, 0x05, 0xab, 0x8a, 0x68, 0xf2, 0x52,
0xbe, 0xff, 0x59, 0x33, 0xd4, 0x08, 0x82, 0xff, 0x6d, 0xe8, 0x55, 0x20, 0xf9, 0x14, 0xdc, 0x97,
0x49, 0x5e, 0xba, 0x74, 0x30, 0x7d, 0xe7, 0xb5, 0x51, 0x93, 0x1f, 0x92, 0x3c, 0xa6, 0xa8, 0xd1,
0xde, 0x88, 0x79, 0xb1, 0x4a, 0x19, 0x32, 0xd8, 0x7f, 0x8b, 0xb6, 0x21, 0x63, 0xdc, 0x87, 0x0f,
0x4a, 0x41, 0x63, 0x5c, 0x83, 0x68, 0xef, 0xac, 0x38, 0x4f, 0x4b, 0x5a, 0x7b, 0xa7, 0x47, 0x1b,
0x40, 0xe7, 0x97, 0x4a, 0x24, 0xf9, 0xb6, 0xe4, 0x3d, 0x5c, 0x6a, 0x1b, 0xd2, 0xf9, 0x57, 0x07,
0xc5, 0x64, 0x29, 0xe8, 0x9c, 0x5b, 0xe3, 0x37, 0x69, 0x0b, 0x09, 0x36, 0xe0, 0xea, 0x7a, 0xc9,
0x1d, 0xf0, 0xc3, 0xe7, 0x57, 0xf3, 0x17, 0x17, 0xcf, 0x96, 0xcb, 0xe1, 0x1b, 0xe4, 0x2d, 0xe8,
0x63, 0xf8, 0xe4, 0xf2, 0xd9, 0x6c, 0x39, 0x1f, 0x5a, 0x64, 0x00, 0x80, 0xc0, 0xe2, 0x22, 0x7c,
0xf8, 0x60, 0x68, 0xd7, 0xfa, 0xd9, 0xe5, 0xe5, 0x72, 0xe8, 0xd4, 0xfa, 0xeb, 0x90, 0x2e, 0x2e,
0xbe, 0x1f, 0xba, 0xb5, 0x7e, 0xf6, 0x3c, 0x9c, 0x5f, 0x0f, 0xbd, 0xe0, 0x3f, 0x0b, 0x06, 0xc7,
0xf7, 0x05, 0x19, 0x80, 0x9d, 0x94, 0x6d, 0x74, 0xa8, 0x9d, 0xc4, 0xda, 0xa6, 0x5c, 0x6c, 0x8d,
0x4d, 0x1d, 0x5a, 0x06, 0xf5, 0x36, 0x3a, 0xcd, 0x36, 0x6a, 0x4c, 0x1d, 0x76, 0xcc, 0x1c, 0x64,
0xfc, 0x27, 0x43, 0x70, 0x0a, 0x91, 0x9a, 0x16, 0xe8, 0x5f, 0x6d, 0xf0, 0xdf, 0x25, 0xcf, 0xf5,
0xac, 0xb8, 0x70, 0x9f, 0xd6, 0x31, 0xb9, 0x0f, 0x03, 0xc9, 0xd6, 0x85, 0x60, 0x4f, 0x2b, 0x45,
0x17, 0x15, 0x27, 0xa8, 0x2e, 0x1b, 0x1a, 0xcb, 0xdf, 0xe6, 0x29, 0xf2, 0x05, 0xb8, 0x2a, 0xda,
0x56, 0xee, 0x7c, 0xff, 0x96, 0x93, 0x32, 0x09, 0xa3, 0xad, 0x9c, 0xe7, 0x4a, 0x1c, 0x28, 0x4a,
0xf5, 0x89, 0xd9, 0x95, 0x97, 0xd9, 0xe9, 0xe5, 0x88, 0xd7, 0x19, 0x35, 0xec, 0xd9, 0x23, 0xf0,
0xeb, 0xa1, 0x7a, 0x81, 0x2f, 0xd9, 0xc1, 0x4c, 0xad, 0x7f, 0x75, 0xc3, 0x6e, 0x6a, 0x5f, 0xf9,
0xb4, 0x0c, 0x1e, 0xdb, 0x5f, 0x5b, 0xc1, 0x37, 0xe0, 0x61, 0x26, 0xbc, 0x7a, 0x92, 0x8c, 0x49,
0x15, 0x65, 0x3b, 0xd3, 0xea, 0x06, 0x38, 0x4e, 0x60, 0x99, 0x04, 0xd3, 0x6f, 0x01, 0xf4, 0x9b,
0x71, 0x85, 0x25, 0x91, 0x49, 0x75, 0x3d, 0xb7, 0x9e, 0x8a, 0xea, 0x45, 0x39, 0x6b, 0x9d, 0x19,
0x73, 0x85, 0xcd, 0xba, 0xbf, 0x78, 0xf8, 0x08, 0xad, 0x3a, 0xf8, 0xf9, 0xf2, 0x55, 0x00, 0x00,
0x00, 0xff, 0xff, 0x58, 0xae, 0x00, 0xd6, 0xa0, 0x06, 0x00, 0x00,
}
-98
View File
@@ -1,98 +0,0 @@
syntax = "proto3";
option go_package = "proto";
package plugins;
message TsdbQuery {
TimeRange timeRange = 1;
DatasourceInfo datasource = 2;
repeated Query queries = 3;
}
message Query {
string refId = 1;
int64 maxDataPoints = 2;
int64 intervalMs = 3;
string modelJson = 4;
}
message TimeRange {
string fromRaw = 1;
string toRaw = 2;
int64 fromEpochMs = 3;
int64 toEpochMs = 4;
}
message Response {
repeated QueryResult results = 1;
}
message QueryResult {
string error = 1;
string refId = 2;
string metaJson = 3;
repeated TimeSeries series = 4;
repeated Table tables = 5;
}
message Table {
repeated TableColumn columns = 1;
repeated TableRow rows = 2;
}
message TableColumn {
string name = 1;
}
message TableRow {
repeated RowValue values = 1;
}
message RowValue {
enum Kind {
// Field type null.
TYPE_NULL = 0;
// Field type double.
TYPE_DOUBLE = 1;
// Field type int64.
TYPE_INT64 = 2;
// Field type bool.
TYPE_BOOL = 3;
// Field type string.
TYPE_STRING = 4;
// Field type bytes.
TYPE_BYTES = 5;
};
Kind kind = 1;
double doubleValue = 2;
int64 int64Value = 3;
bool boolValue = 4;
string stringValue = 5;
bytes bytesValue = 6;
}
message DatasourceInfo {
int64 id = 1;
int64 orgId = 2;
string name = 3;
string type = 4;
string url = 5;
string jsonData = 6;
string secureJsonData = 7;
}
message TimeSeries {
string name = 1;
map<string, string> tags = 2;
repeated Point points = 3;
}
message Point {
int64 timestamp = 1;
double value = 2;
}
service TsdbPlugin {
rpc Query(TsdbQuery) returns (Response);
}
+71 -59
View File
@@ -5,8 +5,8 @@ import (
"context"
"database/sql"
"fmt"
"reflect"
"strconv"
"time"
"github.com/go-sql-driver/mysql"
@@ -73,24 +73,36 @@ func (e MysqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
table.Columns[i].Text = name
}
columnTypes, err := rows.ColumnTypes()
if err != nil {
return err
}
rowLimit := 1000000
rowCount := 0
timeIndex := -1
// check if there is a column named time
for i, col := range columnNames {
switch col {
case "time_sec":
timeIndex = i
}
}
for ; rows.Next(); rowCount++ {
if rowCount > rowLimit {
return fmt.Errorf("MySQL query row limit exceeded, limit %d", rowLimit)
}
values, err := e.getTypedRowData(columnTypes, rows)
values, err := e.getTypedRowData(rows)
if err != nil {
return err
}
// for annotations, convert to epoch
if timeIndex != -1 {
switch value := values[timeIndex].(type) {
case time.Time:
values[timeIndex] = float64(value.UnixNano() / 1e9)
}
}
table.Rows = append(table.Rows, values)
}
@@ -99,60 +111,20 @@ func (e MysqlQueryEndpoint) transformToTable(query *tsdb.Query, rows *core.Rows,
return nil
}
func (e MysqlQueryEndpoint) getTypedRowData(types []*sql.ColumnType, rows *core.Rows) (tsdb.RowValues, error) {
func (e MysqlQueryEndpoint) getTypedRowData(rows *core.Rows) (tsdb.RowValues, error) {
types, err := rows.ColumnTypes()
if err != nil {
return nil, err
}
values := make([]interface{}, len(types))
for i, stype := range types {
e.log.Debug("type", "type", stype)
switch stype.DatabaseTypeName() {
case mysql.FieldTypeNameTiny:
values[i] = new(int8)
case mysql.FieldTypeNameInt24:
values[i] = new(int32)
case mysql.FieldTypeNameShort:
values[i] = new(int16)
case mysql.FieldTypeNameVarString:
values[i] = new(string)
case mysql.FieldTypeNameVarChar:
values[i] = new(string)
case mysql.FieldTypeNameLong:
values[i] = new(int)
case mysql.FieldTypeNameLongLong:
values[i] = new(int64)
case mysql.FieldTypeNameDouble:
values[i] = new(float64)
case mysql.FieldTypeNameDecimal:
values[i] = new(float32)
case mysql.FieldTypeNameNewDecimal:
values[i] = new(float64)
case mysql.FieldTypeNameFloat:
values[i] = new(float64)
case mysql.FieldTypeNameTimestamp:
values[i] = new(time.Time)
case mysql.FieldTypeNameDateTime:
values[i] = new(time.Time)
case mysql.FieldTypeNameTime:
values[i] = new(string)
case mysql.FieldTypeNameYear:
values[i] = new(int16)
case mysql.FieldTypeNameNULL:
values[i] = nil
case mysql.FieldTypeNameBit:
for i := range values {
scanType := types[i].ScanType()
values[i] = reflect.New(scanType).Interface()
if types[i].DatabaseTypeName() == "BIT" {
values[i] = new([]byte)
case mysql.FieldTypeNameBLOB:
values[i] = new(string)
case mysql.FieldTypeNameTinyBLOB:
values[i] = new(string)
case mysql.FieldTypeNameMediumBLOB:
values[i] = new(string)
case mysql.FieldTypeNameLongBLOB:
values[i] = new(string)
case mysql.FieldTypeNameString:
values[i] = new(string)
case mysql.FieldTypeNameDate:
values[i] = new(string)
default:
return nil, fmt.Errorf("Database type %s not supported", stype.DatabaseTypeName())
}
}
@@ -160,14 +132,54 @@ func (e MysqlQueryEndpoint) getTypedRowData(types []*sql.ColumnType, rows *core.
return nil, err
}
for i := 0; i < len(types); i++ {
typeName := reflect.ValueOf(values[i]).Type().String()
switch typeName {
case "*sql.RawBytes":
values[i] = string(*values[i].(*sql.RawBytes))
case "*mysql.NullTime":
sqlTime := (*values[i].(*mysql.NullTime))
if sqlTime.Valid {
values[i] = sqlTime.Time
} else {
values[i] = nil
}
case "*sql.NullInt64":
nullInt64 := (*values[i].(*sql.NullInt64))
if nullInt64.Valid {
values[i] = nullInt64.Int64
} else {
values[i] = nil
}
case "*sql.NullFloat64":
nullFloat64 := (*values[i].(*sql.NullFloat64))
if nullFloat64.Valid {
values[i] = nullFloat64.Float64
} else {
values[i] = nil
}
}
if types[i].DatabaseTypeName() == "DECIMAL" {
f, err := strconv.ParseFloat(values[i].(string), 64)
if err == nil {
values[i] = f
} else {
values[i] = nil
}
}
}
return values, nil
}
func (e MysqlQueryEndpoint) transformToTimeSeries(query *tsdb.Query, rows *core.Rows, result *tsdb.QueryResult) error {
pointsBySeries := make(map[string]*tsdb.TimeSeries)
seriesByQueryOrder := list.New()
columnNames, err := rows.Columns()
columnNames, err := rows.Columns()
if err != nil {
return err
}
+43 -32
View File
@@ -30,19 +30,19 @@ func TestMySQL(t *testing.T) {
defer sess.Close()
sql := "CREATE TABLE `mysql_types` ("
sql += "`atinyint` tinyint(1),"
sql += "`avarchar` varchar(3),"
sql += "`atinyint` tinyint(1) NOT NULL,"
sql += "`avarchar` varchar(3) NOT NULL,"
sql += "`achar` char(3),"
sql += "`amediumint` mediumint,"
sql += "`asmallint` smallint,"
sql += "`abigint` bigint,"
sql += "`aint` int(11),"
sql += "`amediumint` mediumint NOT NULL,"
sql += "`asmallint` smallint NOT NULL,"
sql += "`abigint` bigint NOT NULL,"
sql += "`aint` int(11) NOT NULL,"
sql += "`adouble` double(10,2),"
sql += "`anewdecimal` decimal(10,2),"
sql += "`afloat` float(10,2),"
sql += "`afloat` float(10,2) NOT NULL,"
sql += "`atimestamp` timestamp NOT NULL,"
sql += "`adatetime` datetime,"
sql += "`atime` time,"
sql += "`adatetime` datetime NOT NULL,"
sql += "`atime` time NOT NULL,"
// sql += "`ayear` year," // Crashes xorm when running cleandb
sql += "`abit` bit(1),"
sql += "`atinytext` tinytext,"
@@ -55,7 +55,12 @@ func TestMySQL(t *testing.T) {
sql += "`alongblob` longblob,"
sql += "`aenum` enum('val1', 'val2'),"
sql += "`aset` set('a', 'b', 'c', 'd'),"
sql += "`adate` date"
sql += "`adate` date,"
sql += "`time_sec` datetime(6),"
sql += "`aintnull` int(11),"
sql += "`afloatnull` float(10,2),"
sql += "`avarcharnull` varchar(3),"
sql += "`adecimalnull` decimal(10,2)"
sql += ") ENGINE=InnoDB DEFAULT CHARSET=latin1;"
_, err := sess.Exec(sql)
So(err, ShouldBeNil)
@@ -64,11 +69,11 @@ func TestMySQL(t *testing.T) {
sql += "(`atinyint`, `avarchar`, `achar`, `amediumint`, `asmallint`, `abigint`, `aint`, `adouble`, "
sql += "`anewdecimal`, `afloat`, `adatetime`, `atimestamp`, `atime`, `abit`, `atinytext`, "
sql += "`atinyblob`, `atext`, `ablob`, `amediumtext`, `amediumblob`, `alongtext`, `alongblob`, "
sql += "`aenum`, `aset`, `adate`) "
sql += "`aenum`, `aset`, `adate`, `time_sec`) "
sql += "VALUES(1, 'abc', 'def', 1, 10, 100, 1420070400, 1.11, "
sql += "2.22, 3.33, now(), current_timestamp(), '11:11:11', 1, 'tinytext', "
sql += "'tinyblob', 'text', 'blob', 'mediumtext', 'mediumblob', 'longtext', 'longblob', "
sql += "'val2', 'a,b', curdate());"
sql += "'val2', 'a,b', curdate(), '2018-01-01 00:01:01.123456');"
_, err = sess.Exec(sql)
So(err, ShouldBeNil)
@@ -90,32 +95,38 @@ func TestMySQL(t *testing.T) {
So(err, ShouldBeNil)
column := queryResult.Tables[0].Rows[0]
So(*column[0].(*int8), ShouldEqual, 1)
So(*column[1].(*string), ShouldEqual, "abc")
So(*column[2].(*string), ShouldEqual, "def")
So(column[1].(string), ShouldEqual, "abc")
So(column[2].(string), ShouldEqual, "def")
So(*column[3].(*int32), ShouldEqual, 1)
So(*column[4].(*int16), ShouldEqual, 10)
So(*column[5].(*int64), ShouldEqual, 100)
So(*column[6].(*int), ShouldEqual, 1420070400)
So(*column[7].(*float64), ShouldEqual, 1.11)
So(*column[8].(*float64), ShouldEqual, 2.22)
So(*column[9].(*float64), ShouldEqual, 3.33)
So(*column[6].(*int32), ShouldEqual, 1420070400)
So(column[7].(float64), ShouldEqual, 1.11)
So(column[8].(float64), ShouldEqual, 2.22)
So(*column[9].(*float32), ShouldEqual, 3.33)
_, offset := time.Now().Zone()
So((*column[10].(*time.Time)), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second))
So(*column[11].(*time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second))
So(*column[12].(*string), ShouldEqual, "11:11:11")
So(column[10].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second))
So(column[11].(time.Time), ShouldHappenWithin, time.Duration(10*time.Second), time.Now().Add(time.Duration(offset)*time.Second))
So(column[12].(string), ShouldEqual, "11:11:11")
So(*column[13].(*[]byte), ShouldHaveSameTypeAs, []byte{1})
So(*column[14].(*string), ShouldEqual, "tinytext")
So(*column[15].(*string), ShouldEqual, "tinyblob")
So(*column[16].(*string), ShouldEqual, "text")
So(*column[17].(*string), ShouldEqual, "blob")
So(*column[18].(*string), ShouldEqual, "mediumtext")
So(*column[19].(*string), ShouldEqual, "mediumblob")
So(*column[20].(*string), ShouldEqual, "longtext")
So(*column[21].(*string), ShouldEqual, "longblob")
So(*column[22].(*string), ShouldEqual, "val2")
So(*column[23].(*string), ShouldEqual, "a,b")
So(*column[24].(*string), ShouldEqual, time.Now().Format("2006-01-02T00:00:00Z"))
So(column[14].(string), ShouldEqual, "tinytext")
So(column[15].(string), ShouldEqual, "tinyblob")
So(column[16].(string), ShouldEqual, "text")
So(column[17].(string), ShouldEqual, "blob")
So(column[18].(string), ShouldEqual, "mediumtext")
So(column[19].(string), ShouldEqual, "mediumblob")
So(column[20].(string), ShouldEqual, "longtext")
So(column[21].(string), ShouldEqual, "longblob")
So(column[22].(string), ShouldEqual, "val2")
So(column[23].(string), ShouldEqual, "a,b")
So(column[24].(time.Time).Format("2006-01-02T00:00:00Z"), ShouldEqual, time.Now().Format("2006-01-02T00:00:00Z"))
So(column[25].(float64), ShouldEqual, 1514764861)
So(column[26], ShouldEqual, nil)
So(column[27], ShouldEqual, nil)
So(column[28], ShouldEqual, "")
So(column[29], ShouldEqual, nil)
})
})
}