dashfolders: merge conflict
This commit is contained in:
@@ -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
@@ -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")
|
||||
})
|
||||
}
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -8,6 +8,7 @@ type SystemStats struct {
|
||||
Orgs int64
|
||||
Playlists int64
|
||||
Alerts int64
|
||||
Stars int64
|
||||
}
|
||||
|
||||
type DataSourceStats struct {
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
}
|
||||
+39
-33
@@ -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)
|
||||
+19
-18
@@ -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)
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
+2
-2
@@ -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)
|
||||
|
||||
@@ -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
|
||||
}
|
||||
|
||||
@@ -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(¤t); err != nil {
|
||||
if _, err = sess.ID(cmd.Id).Get(¤t); 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")
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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"`
|
||||
|
||||
@@ -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
@@ -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()),
|
||||
|
||||
@@ -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)
|
||||
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
}
|
||||
@@ -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
@@ -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
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user