mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
datasources as cfg: tests for insert/updating datasources
This commit is contained in:
parent
39b6c04643
commit
0f29b8ac83
@ -1,28 +1,30 @@
|
||||
# - name: Graphite202
|
||||
# type: graphite
|
||||
# access: proxy
|
||||
# url: http://localhost:8080
|
||||
# password:
|
||||
# user:
|
||||
# database:
|
||||
# basicAuth:
|
||||
# basicAuthUser:
|
||||
# basicAuthPassword:
|
||||
# withCredentials:
|
||||
# isDefault: true
|
||||
# jsonData: {}
|
||||
# secureJsonFields: {}
|
||||
# - name: Prometheus
|
||||
# type: prometheus
|
||||
# access: proxy
|
||||
# url: http://localhost:9090
|
||||
# password:
|
||||
# user:
|
||||
# database:
|
||||
# basicAuth:
|
||||
# basicAuthUser:
|
||||
# basicAuthPassword:
|
||||
# withCredentials:
|
||||
# isDefault: true
|
||||
# jsonData: {}
|
||||
# secureJsonFields: {}
|
||||
purgeOtherDatasources: false
|
||||
datasources:
|
||||
- name: Graphite202
|
||||
type: graphite
|
||||
access: proxy
|
||||
url: http://localhost:8080
|
||||
password:
|
||||
user:
|
||||
database:
|
||||
basicAuth:
|
||||
basicAuthUser:
|
||||
basicAuthPassword:
|
||||
withCredentials:
|
||||
isDefault: true
|
||||
jsonData: {}
|
||||
secureJsonFields: {}
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://localhost:9090
|
||||
password:
|
||||
user:
|
||||
database:
|
||||
basicAuth:
|
||||
basicAuthUser:
|
||||
basicAuthPassword:
|
||||
withCredentials:
|
||||
isDefault: true
|
||||
jsonData: {}
|
||||
secureJsonFields: {}
|
||||
|
@ -157,6 +157,10 @@ type GetDataSourcesQuery struct {
|
||||
Result []*DataSource
|
||||
}
|
||||
|
||||
type GetAllDataSourcesQuery struct {
|
||||
Result []*DataSource
|
||||
}
|
||||
|
||||
type GetDataSourceByIdQuery struct {
|
||||
Id int64
|
||||
OrgId int64
|
||||
|
@ -13,6 +13,7 @@ import (
|
||||
|
||||
func init() {
|
||||
bus.AddHandler("sql", GetDataSources)
|
||||
bus.AddHandler("sql", GetAllDataSources)
|
||||
bus.AddHandler("sql", AddDataSource)
|
||||
bus.AddHandler("sql", DeleteDataSourceById)
|
||||
bus.AddHandler("sql", DeleteDataSourceByName)
|
||||
@ -54,6 +55,13 @@ func GetDataSources(query *m.GetDataSourcesQuery) error {
|
||||
return sess.Find(&query.Result)
|
||||
}
|
||||
|
||||
func GetAllDataSources(query *m.GetAllDataSourcesQuery) error {
|
||||
sess := x.Limit(1000, 0).Asc("name")
|
||||
|
||||
query.Result = make([]*m.DataSource, 0)
|
||||
return sess.Find(&query.Result)
|
||||
}
|
||||
|
||||
func DeleteDataSourceById(cmd *m.DeleteDataSourceByIdCommand) error {
|
||||
return inTransaction(func(sess *DBSession) error {
|
||||
var rawSql = "DELETE FROM data_source WHERE id=? and org_id=?"
|
||||
|
182
pkg/setting/datasources/datasources.go
Normal file
182
pkg/setting/datasources/datasources.go
Normal file
@ -0,0 +1,182 @@
|
||||
package datasources
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
// TODO: secure jsonData
|
||||
// TODO: auto reload on file changes
|
||||
|
||||
type DatasourcesAsConfig struct {
|
||||
PurgeOtherDatasources bool
|
||||
Datasources []models.DataSource
|
||||
}
|
||||
|
||||
func Init(configPath string) (error, io.Closer) {
|
||||
|
||||
dc := NewDatasourceConfiguration()
|
||||
dc.applyChanges(configPath)
|
||||
|
||||
return nil, ioutil.NopCloser(nil)
|
||||
}
|
||||
|
||||
type DatasourceConfigurator struct {
|
||||
log log.Logger
|
||||
cfgProvider configProvider
|
||||
repository datasourceRepository
|
||||
}
|
||||
|
||||
func NewDatasourceConfiguration() DatasourceConfigurator {
|
||||
return newDatasourceConfiguration(log.New("setting.datasource"), diskConfigReader{}, sqlDatasourceRepository{})
|
||||
}
|
||||
|
||||
func newDatasourceConfiguration(log log.Logger, cfgProvider configProvider, repo datasourceRepository) DatasourceConfigurator {
|
||||
return DatasourceConfigurator{
|
||||
log: log.New("setting.datasource"),
|
||||
repository: repo,
|
||||
cfgProvider: cfgProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DatasourceConfigurator) applyChanges(configPath string) error {
|
||||
datasources, err := dc.cfgProvider.readConfig(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
//read all datasources
|
||||
//delete datasources not in list
|
||||
|
||||
for _, ds := range datasources.Datasources {
|
||||
if ds.OrgId == 0 {
|
||||
ds.OrgId = 1
|
||||
}
|
||||
|
||||
query := &models.GetDataSourceByNameQuery{Name: ds.Name, OrgId: ds.OrgId}
|
||||
err := dc.repository.get(query)
|
||||
if err != nil && err != models.ErrDataSourceNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if query.Result == nil {
|
||||
dc.log.Info("inserting ", "name", ds.Name)
|
||||
insertCmd := createInsertCommand(ds)
|
||||
if err := dc.repository.insert(insertCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
dc.log.Info("updating", "name", ds.Name)
|
||||
updateCmd := createUpdateCommand(ds, query.Result.Id)
|
||||
if err := dc.repository.update(updateCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func createInsertCommand(ds models.DataSource) *models.AddDataSourceCommand {
|
||||
return &models.AddDataSourceCommand{
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
Type: ds.Type,
|
||||
Access: ds.Access,
|
||||
Url: ds.Url,
|
||||
Password: ds.Password,
|
||||
User: ds.User,
|
||||
Database: ds.Database,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
BasicAuthPassword: ds.BasicAuthPassword,
|
||||
WithCredentials: ds.WithCredentials,
|
||||
IsDefault: ds.IsDefault,
|
||||
JsonData: ds.JsonData,
|
||||
}
|
||||
}
|
||||
|
||||
func createUpdateCommand(ds models.DataSource, id int64) *models.UpdateDataSourceCommand {
|
||||
return &models.UpdateDataSourceCommand{
|
||||
Id: id,
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
Type: ds.Type,
|
||||
Access: ds.Access,
|
||||
Url: ds.Url,
|
||||
Password: ds.Password,
|
||||
User: ds.User,
|
||||
Database: ds.Database,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
BasicAuthPassword: ds.BasicAuthPassword,
|
||||
WithCredentials: ds.WithCredentials,
|
||||
IsDefault: ds.IsDefault,
|
||||
JsonData: ds.JsonData,
|
||||
}
|
||||
}
|
||||
|
||||
type datasourceRepository interface {
|
||||
insert(*models.AddDataSourceCommand) error
|
||||
update(*models.UpdateDataSourceCommand) error
|
||||
delete(*models.DeleteDataSourceByIdCommand) error
|
||||
get(*models.GetDataSourceByNameQuery) error
|
||||
loadAllDatasources() ([]*models.DataSource, error)
|
||||
}
|
||||
|
||||
type configProvider interface {
|
||||
readConfig(string) (*DatasourcesAsConfig, error)
|
||||
}
|
||||
|
||||
type sqlDatasourceRepository struct{}
|
||||
type diskConfigReader struct{}
|
||||
|
||||
func (diskConfigReader) readConfig(path string) (*DatasourcesAsConfig, error) {
|
||||
filename, _ := filepath.Abs(path)
|
||||
yamlFile, err := ioutil.ReadFile(filename)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var datasources *DatasourcesAsConfig
|
||||
|
||||
err = yaml.Unmarshal(yamlFile, &datasources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return datasources, nil
|
||||
}
|
||||
|
||||
func (sqlDatasourceRepository) delete(cmd *models.DeleteDataSourceByIdCommand) error {
|
||||
return bus.Dispatch(cmd)
|
||||
}
|
||||
|
||||
func (sqlDatasourceRepository) update(cmd *models.UpdateDataSourceCommand) error {
|
||||
return bus.Dispatch(cmd)
|
||||
}
|
||||
|
||||
func (sqlDatasourceRepository) insert(cmd *models.AddDataSourceCommand) error {
|
||||
return bus.Dispatch(cmd)
|
||||
}
|
||||
|
||||
func (sqlDatasourceRepository) get(cmd *models.GetDataSourceByNameQuery) error {
|
||||
return bus.Dispatch(cmd)
|
||||
}
|
||||
|
||||
func (sqlDatasourceRepository) loadAllDatasources() ([]*models.DataSource, error) {
|
||||
dss := &models.GetAllDataSourcesQuery{}
|
||||
if err := bus.Dispatch(dss); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return dss.Result, nil
|
||||
}
|
152
pkg/setting/datasources/datasources_test.go
Normal file
152
pkg/setting/datasources/datasources_test.go
Normal file
@ -0,0 +1,152 @@
|
||||
package datasources
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
. "github.com/smartystreets/goconvey/convey"
|
||||
)
|
||||
|
||||
var logger log.Logger = log.New("fake.logger")
|
||||
|
||||
func TestDatasourceAsConfig(t *testing.T) {
|
||||
Convey("Testing datasource as configuration", t, func() {
|
||||
fakeCfg := &fakeConfig{}
|
||||
|
||||
fakeRepo := &fakeRepository{}
|
||||
|
||||
Convey("One configured datasource", func() {
|
||||
fakeCfg.cfg = &DatasourcesAsConfig{
|
||||
PurgeOtherDatasources: false,
|
||||
Datasources: []models.DataSource{
|
||||
models.DataSource{Name: "graphite", OrgId: 1},
|
||||
},
|
||||
}
|
||||
|
||||
Convey("no datasource in database", func() {
|
||||
dc := newDatasourceConfiguration(logger, fakeCfg, fakeRepo)
|
||||
err := dc.applyChanges("mock/config.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
So(len(fakeRepo.deleted), ShouldEqual, 0)
|
||||
So(len(fakeRepo.inserted), ShouldEqual, 1)
|
||||
So(len(fakeRepo.updated), ShouldEqual, 0)
|
||||
})
|
||||
|
||||
Convey("One datasource in database with same name", func() {
|
||||
fakeRepo.loadAll = []*models.DataSource{
|
||||
&models.DataSource{Name: "graphite", OrgId: 1, Id: 1},
|
||||
}
|
||||
|
||||
Convey("should update one datasource", func() {
|
||||
dc := newDatasourceConfiguration(logger, fakeCfg, fakeRepo)
|
||||
err := dc.applyChanges("mock/config.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
So(len(fakeRepo.deleted), ShouldEqual, 0)
|
||||
So(len(fakeRepo.inserted), ShouldEqual, 0)
|
||||
So(len(fakeRepo.updated), ShouldEqual, 1)
|
||||
})
|
||||
})
|
||||
|
||||
Convey("One datasource in database with new name", func() {
|
||||
fakeRepo.loadAll = []*models.DataSource{
|
||||
&models.DataSource{Name: "old-graphite", OrgId: 1, Id: 1},
|
||||
}
|
||||
|
||||
Convey("should update one datasource", func() {
|
||||
dc := newDatasourceConfiguration(logger, fakeCfg, fakeRepo)
|
||||
err := dc.applyChanges("mock/config.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
So(len(fakeRepo.deleted), ShouldEqual, 0)
|
||||
So(len(fakeRepo.inserted), ShouldEqual, 1)
|
||||
So(len(fakeRepo.updated), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Convey("Two configured datasource and purge others ", func() {
|
||||
fakeCfg.cfg = &DatasourcesAsConfig{
|
||||
PurgeOtherDatasources: true,
|
||||
Datasources: []models.DataSource{
|
||||
models.DataSource{Name: "graphite", OrgId: 1},
|
||||
models.DataSource{Name: "prometheus", OrgId: 1},
|
||||
},
|
||||
}
|
||||
|
||||
Convey("two other datasources in database", func() {
|
||||
fakeRepo.loadAll = []*models.DataSource{
|
||||
&models.DataSource{Name: "old-graphite", OrgId: 1, Id: 1},
|
||||
&models.DataSource{Name: "old-graphite2", OrgId: 1, Id: 2},
|
||||
}
|
||||
|
||||
Convey("should have two new datasources", func() {
|
||||
dc := newDatasourceConfiguration(logger, fakeCfg, fakeRepo)
|
||||
err := dc.applyChanges("mock/config.yaml")
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
So(len(fakeRepo.deleted), ShouldEqual, 2)
|
||||
So(len(fakeRepo.inserted), ShouldEqual, 2)
|
||||
So(len(fakeRepo.updated), ShouldEqual, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type fakeRepository struct {
|
||||
inserted []*models.AddDataSourceCommand
|
||||
deleted []*models.DeleteDataSourceByIdCommand
|
||||
updated []*models.UpdateDataSourceCommand
|
||||
|
||||
loadAll []*models.DataSource
|
||||
}
|
||||
|
||||
type fakeConfig struct {
|
||||
cfg *DatasourcesAsConfig
|
||||
}
|
||||
|
||||
func (fc *fakeConfig) readConfig(path string) (*DatasourcesAsConfig, error) {
|
||||
return fc.cfg, nil
|
||||
}
|
||||
|
||||
func (fc *fakeRepository) delete(cmd *models.DeleteDataSourceByIdCommand) error {
|
||||
fc.deleted = append(fc.deleted, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fc *fakeRepository) update(cmd *models.UpdateDataSourceCommand) error {
|
||||
fc.updated = append(fc.updated, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fc *fakeRepository) insert(cmd *models.AddDataSourceCommand) error {
|
||||
fc.inserted = append(fc.inserted, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fc *fakeRepository) loadAllDatasources() ([]*models.DataSource, error) {
|
||||
return fc.loadAll, nil
|
||||
}
|
||||
|
||||
func (fc *fakeRepository) get(cmd *models.GetDataSourceByNameQuery) error {
|
||||
for _, v := range fc.loadAll {
|
||||
if cmd.Name == v.Name && cmd.OrgId == v.OrgId {
|
||||
cmd.Result = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return models.ErrDataSourceNotFound
|
||||
}
|
@ -1,114 +0,0 @@
|
||||
package datasources
|
||||
|
||||
import (
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
yaml "gopkg.in/yaml.v2"
|
||||
)
|
||||
|
||||
var (
|
||||
logger log.Logger
|
||||
)
|
||||
|
||||
// TODO: secure jsonData
|
||||
// TODO: auto reload on file changes
|
||||
|
||||
func Init(configPath string) (error, io.Closer) {
|
||||
logger = log.New("setting.datasource")
|
||||
|
||||
datasources, err := readDatasources(configPath)
|
||||
if err != nil {
|
||||
return err, ioutil.NopCloser(nil)
|
||||
}
|
||||
|
||||
for _, ds := range datasources {
|
||||
query := &models.GetDataSourceByNameQuery{Name: ds.Name}
|
||||
err := bus.Dispatch(query)
|
||||
if err != nil && err != models.ErrDataSourceNotFound {
|
||||
return err, ioutil.NopCloser(nil)
|
||||
}
|
||||
|
||||
if ds.OrgId == 0 {
|
||||
ds.OrgId = 1
|
||||
}
|
||||
|
||||
if query.Result == nil {
|
||||
logger.Info("inserting ", "name", ds.Name)
|
||||
insertCmd := insertCommand(ds)
|
||||
if err := bus.Dispatch(insertCmd); err != nil {
|
||||
return err, ioutil.NopCloser(nil)
|
||||
}
|
||||
} else {
|
||||
logger.Info("updating", "name", ds.Name)
|
||||
updateCmd := updateCommand(ds, query.Result.Id)
|
||||
if err := bus.Dispatch(updateCmd); err != nil {
|
||||
return err, ioutil.NopCloser(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return nil, ioutil.NopCloser(nil)
|
||||
}
|
||||
|
||||
func readDatasources(path string) ([]models.DataSource, error) {
|
||||
filename, _ := filepath.Abs(path)
|
||||
yamlFile, err := ioutil.ReadFile(filename)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var datasources []models.DataSource
|
||||
|
||||
err = yaml.Unmarshal(yamlFile, &datasources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return datasources, nil
|
||||
}
|
||||
|
||||
func insertCommand(ds models.DataSource) *models.AddDataSourceCommand {
|
||||
return &models.AddDataSourceCommand{
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
Type: ds.Type,
|
||||
Access: ds.Access,
|
||||
Url: ds.Url,
|
||||
Password: ds.Password,
|
||||
User: ds.User,
|
||||
Database: ds.Database,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
BasicAuthPassword: ds.BasicAuthPassword,
|
||||
WithCredentials: ds.WithCredentials,
|
||||
IsDefault: ds.IsDefault,
|
||||
JsonData: ds.JsonData,
|
||||
}
|
||||
}
|
||||
|
||||
func updateCommand(ds models.DataSource, id int64) *models.UpdateDataSourceCommand {
|
||||
return &models.UpdateDataSourceCommand{
|
||||
Id: id,
|
||||
OrgId: ds.OrgId,
|
||||
Name: ds.Name,
|
||||
Type: ds.Type,
|
||||
Access: ds.Access,
|
||||
Url: ds.Url,
|
||||
Password: ds.Password,
|
||||
User: ds.User,
|
||||
Database: ds.Database,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
BasicAuthPassword: ds.BasicAuthPassword,
|
||||
WithCredentials: ds.WithCredentials,
|
||||
IsDefault: ds.IsDefault,
|
||||
JsonData: ds.JsonData,
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user