mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
datasource as cfg: support globbing
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"errors"
|
||||
"io/ioutil"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
|
||||
@@ -17,67 +18,36 @@ var (
|
||||
ErrInvalidConfigToManyDefault = errors.New("datasource.yaml config is invalid. Only one datasource can be marked as default")
|
||||
)
|
||||
|
||||
func Apply(configPath string) error {
|
||||
dc := NewDatasourceConfiguration()
|
||||
return dc.applyChanges(configPath)
|
||||
func Provision(configDirectory string) error {
|
||||
dc := newDatasourceProvisioner(log.New("provisioning.datasources"))
|
||||
return dc.applyChanges(configDirectory)
|
||||
}
|
||||
|
||||
type DatasourceConfigurator struct {
|
||||
type DatasourceProvisioner struct {
|
||||
log log.Logger
|
||||
cfgProvider configProvider
|
||||
cfgProvider configReader
|
||||
}
|
||||
|
||||
func NewDatasourceConfiguration() DatasourceConfigurator {
|
||||
return newDatasourceConfiguration(log.New("setting.datasource"))
|
||||
}
|
||||
|
||||
func newDatasourceConfiguration(log log.Logger) DatasourceConfigurator {
|
||||
return DatasourceConfigurator{
|
||||
func newDatasourceProvisioner(log log.Logger) DatasourceProvisioner {
|
||||
return DatasourceProvisioner{
|
||||
log: log,
|
||||
cfgProvider: configProvider{},
|
||||
cfgProvider: configReader{},
|
||||
}
|
||||
}
|
||||
|
||||
func (dc *DatasourceConfigurator) applyChanges(configPath string) error {
|
||||
cfg, err := dc.cfgProvider.readConfig(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
defaultCount := 0
|
||||
for i := range cfg.Datasources {
|
||||
if cfg.Datasources[i].OrgId == 0 {
|
||||
cfg.Datasources[i].OrgId = 1
|
||||
}
|
||||
|
||||
if cfg.Datasources[i].IsDefault {
|
||||
defaultCount++
|
||||
if defaultCount > 1 {
|
||||
return ErrInvalidConfigToManyDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd := &models.GetAllDataSourcesQuery{}
|
||||
if err = bus.Dispatch(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
allDatasources := cmd.Result
|
||||
|
||||
if err := dc.deleteDatasourcesNotInConfiguration(cfg, allDatasources); err != nil {
|
||||
func (dc *DatasourceProvisioner) apply(cfg *DatasourcesAsConfig) error {
|
||||
if err := dc.deleteDatasources(cfg.DeleteDatasources); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, ds := range cfg.Datasources {
|
||||
var dbDatasource *models.DataSource
|
||||
for _, ddd := range allDatasources {
|
||||
if ddd.Name == ds.Name && ddd.OrgId == ds.OrgId {
|
||||
dbDatasource = ddd
|
||||
break
|
||||
}
|
||||
cmd := &models.GetDataSourceByNameQuery{OrgId: ds.OrgId, Name: ds.Name}
|
||||
err := bus.Dispatch(cmd)
|
||||
if err != nil && err != models.ErrDataSourceNotFound {
|
||||
return err
|
||||
}
|
||||
|
||||
if dbDatasource == nil {
|
||||
if err == models.ErrDataSourceNotFound {
|
||||
dc.log.Info("inserting datasource from configuration ", "name", ds.Name)
|
||||
insertCmd := createInsertCommand(ds)
|
||||
if err := bus.Dispatch(insertCmd); err != nil {
|
||||
@@ -85,7 +55,7 @@ func (dc *DatasourceConfigurator) applyChanges(configPath string) error {
|
||||
}
|
||||
} else {
|
||||
dc.log.Debug("updating datasource from configuration", "name", ds.Name)
|
||||
updateCmd := createUpdateCommand(ds, dbDatasource.Id)
|
||||
updateCmd := createUpdateCommand(ds, cmd.Result.Id)
|
||||
if err := bus.Dispatch(updateCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -95,44 +65,83 @@ func (dc *DatasourceConfigurator) applyChanges(configPath string) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (dc *DatasourceConfigurator) deleteDatasourcesNotInConfiguration(cfg *DatasourcesAsConfig, allDatasources []*models.DataSource) error {
|
||||
if cfg.PurgeOtherDatasources {
|
||||
for _, dbDS := range allDatasources {
|
||||
delete := true
|
||||
for _, cfgDS := range cfg.Datasources {
|
||||
if dbDS.Name == cfgDS.Name && dbDS.OrgId == cfgDS.OrgId {
|
||||
delete = false
|
||||
}
|
||||
}
|
||||
func (dc *DatasourceProvisioner) applyChanges(configPath string) error {
|
||||
configs, err := dc.cfgProvider.readConfig(configPath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if delete {
|
||||
dc.log.Info("deleting datasource from configuration", "name", dbDS.Name)
|
||||
cmd := &models.DeleteDataSourceByIdCommand{Id: dbDS.Id, OrgId: dbDS.OrgId}
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
for _, cfg := range configs {
|
||||
if err := dc.apply(cfg); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type configProvider struct{}
|
||||
func (dc *DatasourceProvisioner) deleteDatasources(dsToDelete []*DeleteDatasourceConfig) error {
|
||||
for _, ds := range dsToDelete {
|
||||
cmd := &models.DeleteDataSourceByNameCommand{OrgId: ds.OrgId, Name: ds.Name}
|
||||
if err := bus.Dispatch(cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
func (configProvider) readConfig(path string) (*DatasourcesAsConfig, error) {
|
||||
filename, _ := filepath.Abs(path)
|
||||
yamlFile, err := ioutil.ReadFile(filename)
|
||||
if cmd.DeletedDatasourcesCount > 0 {
|
||||
dc.log.Info("deleted datasource based on configuration", "name", ds.Name)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
type configReader struct{}
|
||||
|
||||
func (configReader) readConfig(path string) ([]*DatasourcesAsConfig, error) {
|
||||
files, err := ioutil.ReadDir(path)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
var datasources *DatasourcesAsConfig
|
||||
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()))
|
||||
yamlFile, err := ioutil.ReadFile(filename)
|
||||
|
||||
err = yaml.Unmarshal(yamlFile, &datasources)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var datasource *DatasourcesAsConfig
|
||||
err = yaml.Unmarshal(yamlFile, &datasource)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
datasources = append(datasources, datasource)
|
||||
}
|
||||
}
|
||||
|
||||
defaultCount := 0
|
||||
for _, cfg := range datasources {
|
||||
for _, ds := range cfg.Datasources {
|
||||
if ds.OrgId == 0 {
|
||||
ds.OrgId = 1
|
||||
}
|
||||
|
||||
if ds.IsDefault {
|
||||
defaultCount++
|
||||
if defaultCount > 1 {
|
||||
return nil, ErrInvalidConfigToManyDefault
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for _, ds := range cfg.DeleteDatasources {
|
||||
if ds.OrgId == 0 {
|
||||
ds.OrgId = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return datasources, nil
|
||||
|
||||
@@ -13,10 +13,11 @@ import (
|
||||
var (
|
||||
logger log.Logger = log.New("fake.logger")
|
||||
oneDatasourcesConfig string = ""
|
||||
twoDatasourcesConfig string = "./test-configs/two-datasources.yaml"
|
||||
twoDatasourcesConfigPurgeOthers string = "./test-configs/two-datasources-purge-others.yaml"
|
||||
doubleDatasourcesConfig string = "./test-configs/double-default-datasources.yaml"
|
||||
allProperties string = "./test-configs/all-properties.yaml"
|
||||
twoDatasourcesConfig string = "./test-configs/two-datasources"
|
||||
twoDatasourcesConfigPurgeOthers string = "./test-configs/insert-two-delete-two"
|
||||
doubleDatasourcesConfig string = "./test-configs/double-default"
|
||||
allProperties string = "./test-configs/all-properties"
|
||||
brokenYaml string = "./test-configs/broken-yaml"
|
||||
|
||||
fakeRepo *fakeRepository
|
||||
)
|
||||
@@ -33,7 +34,7 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
|
||||
Convey("One configured datasource", func() {
|
||||
Convey("no datasource in database", func() {
|
||||
dc := newDatasourceConfiguration(logger)
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(twoDatasourcesConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
@@ -50,7 +51,7 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
Convey("should update one datasource", func() {
|
||||
dc := newDatasourceConfiguration(logger)
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(twoDatasourcesConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
@@ -63,7 +64,7 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
Convey("Two datasources with is_default", func() {
|
||||
dc := newDatasourceConfiguration(logger)
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(doubleDatasourcesConfig)
|
||||
Convey("should raise error", func() {
|
||||
So(err, ShouldEqual, ErrInvalidConfigToManyDefault)
|
||||
@@ -79,7 +80,7 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
Convey("should have two new datasources", func() {
|
||||
dc := newDatasourceConfiguration(logger)
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(twoDatasourcesConfigPurgeOthers)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
@@ -100,7 +101,7 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
Convey("should have two new datasources", func() {
|
||||
dc := newDatasourceConfiguration(logger)
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(twoDatasourcesConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
@@ -113,16 +114,22 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
})
|
||||
})
|
||||
|
||||
Convey("can read all properties", func() {
|
||||
Convey("broken yaml should return error", func() {
|
||||
_, err := configReader{}.readConfig(brokenYaml)
|
||||
So(err, ShouldNotBeNil)
|
||||
})
|
||||
|
||||
cfgProvifer := configProvider{}
|
||||
Convey("can read all properties", func() {
|
||||
cfgProvifer := configReader{}
|
||||
cfg, err := cfgProvifer.readConfig(allProperties)
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
}
|
||||
|
||||
So(cfg.PurgeOtherDatasources, ShouldBeTrue)
|
||||
ds := cfg.Datasources[0]
|
||||
So(len(cfg), ShouldEqual, 2)
|
||||
|
||||
dsCfg := cfg[0]
|
||||
ds := dsCfg.Datasources[0]
|
||||
|
||||
So(ds.Name, ShouldEqual, "name")
|
||||
So(ds.Type, ShouldEqual, "type")
|
||||
@@ -138,19 +145,22 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
So(ds.WithCredentials, ShouldBeTrue)
|
||||
So(ds.IsDefault, ShouldBeTrue)
|
||||
So(ds.Editable, ShouldBeTrue)
|
||||
|
||||
dstwo := cfg[1].Datasources[0]
|
||||
So(dstwo.Name, ShouldEqual, "name2")
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
type fakeRepository struct {
|
||||
inserted []*models.AddDataSourceCommand
|
||||
deleted []*models.DeleteDataSourceByIdCommand
|
||||
deleted []*models.DeleteDataSourceByNameCommand
|
||||
updated []*models.UpdateDataSourceCommand
|
||||
|
||||
loadAll []*models.DataSource
|
||||
}
|
||||
|
||||
func mockDelete(cmd *models.DeleteDataSourceByIdCommand) error {
|
||||
func mockDelete(cmd *models.DeleteDataSourceByNameCommand) error {
|
||||
fakeRepo.deleted = append(fakeRepo.deleted, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
purge_other_datasources: true
|
||||
datasources:
|
||||
- name: name
|
||||
type: type
|
||||
@@ -0,0 +1,7 @@
|
||||
purge_other_datasources: true
|
||||
datasources:
|
||||
- name: name2
|
||||
type: type2
|
||||
access: proxy
|
||||
org_id: 2
|
||||
url: url2
|
||||
@@ -0,0 +1,6 @@
|
||||
#sfxzgnsxzcvnbzcvn
|
||||
cvbn
|
||||
cvbn
|
||||
c
|
||||
vbn
|
||||
cvbncvbn
|
||||
@@ -1,12 +0,0 @@
|
||||
purge_other_datasources: false
|
||||
datasources:
|
||||
- name: Graphite
|
||||
type: graphite
|
||||
access: proxy
|
||||
url: http://localhost:8080
|
||||
is_default: true
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://localhost:9090
|
||||
is_default: true
|
||||
@@ -0,0 +1,7 @@
|
||||
datasources:
|
||||
- name: Graphite
|
||||
type: graphite
|
||||
access: proxy
|
||||
url: http://localhost:8080
|
||||
is_default: true
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
datasources:
|
||||
- name: Graphite
|
||||
type: graphite
|
||||
access: proxy
|
||||
url: http://localhost:8080
|
||||
is_default: true
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
datasources:
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://localhost:9090
|
||||
delete_datasources:
|
||||
- name: old-graphite
|
||||
@@ -0,0 +1,7 @@
|
||||
datasources:
|
||||
- name: Graphite
|
||||
type: graphite
|
||||
access: proxy
|
||||
url: http://localhost:8080
|
||||
delete_datasources:
|
||||
- name: old-graphite3
|
||||
@@ -1,10 +0,0 @@
|
||||
purge_other_datasources: true
|
||||
datasources:
|
||||
- name: Graphite
|
||||
type: graphite
|
||||
access: proxy
|
||||
url: http://localhost:8080
|
||||
- name: Prometheus
|
||||
type: prometheus
|
||||
access: proxy
|
||||
url: http://localhost:9090
|
||||
@@ -1,4 +1,3 @@
|
||||
purge_other_datasources: false
|
||||
datasources:
|
||||
- name: Graphite
|
||||
type: graphite
|
||||
@@ -1,2 +0,0 @@
|
||||
purge_other_datasources: false
|
||||
datasources:
|
||||
@@ -4,8 +4,13 @@ import "github.com/grafana/grafana/pkg/models"
|
||||
import "github.com/grafana/grafana/pkg/components/simplejson"
|
||||
|
||||
type DatasourcesAsConfig struct {
|
||||
PurgeOtherDatasources bool `json:"purge_other_datasources" yaml:"purge_other_datasources"`
|
||||
Datasources []DataSourceFromConfig `json:"datasources" yaml:"datasources"`
|
||||
Datasources []*DataSourceFromConfig `json:"datasources" yaml:"datasources"`
|
||||
DeleteDatasources []*DeleteDatasourceConfig `json:"delete_datasources" yaml:"delete_datasources"`
|
||||
}
|
||||
|
||||
type DeleteDatasourceConfig struct {
|
||||
OrgId int64 `json:"org_id" yaml:"org_id"`
|
||||
Name string `json:"name" yaml:"name"`
|
||||
}
|
||||
|
||||
type DataSourceFromConfig struct {
|
||||
@@ -29,7 +34,7 @@ type DataSourceFromConfig struct {
|
||||
Editable bool `json:"editable" yaml:"editable"`
|
||||
}
|
||||
|
||||
func createInsertCommand(ds DataSourceFromConfig) *models.AddDataSourceCommand {
|
||||
func createInsertCommand(ds *DataSourceFromConfig) *models.AddDataSourceCommand {
|
||||
jsonData, err := simplejson.NewJson([]byte(ds.JsonData))
|
||||
if err != nil {
|
||||
jsonData = simplejson.New()
|
||||
@@ -55,7 +60,7 @@ func createInsertCommand(ds DataSourceFromConfig) *models.AddDataSourceCommand {
|
||||
}
|
||||
}
|
||||
|
||||
func createUpdateCommand(ds DataSourceFromConfig, id int64) *models.UpdateDataSourceCommand {
|
||||
func createUpdateCommand(ds *DataSourceFromConfig, id int64) *models.UpdateDataSourceCommand {
|
||||
jsonData, err := simplejson.NewJson([]byte(ds.JsonData))
|
||||
if err != nil {
|
||||
jsonData = simplejson.New()
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
package provisioning
|
||||
|
||||
import (
|
||||
"path/filepath"
|
||||
|
||||
"github.com/grafana/grafana/pkg/log"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/datasources"
|
||||
)
|
||||
@@ -11,6 +9,6 @@ var (
|
||||
logger log.Logger = log.New("services.provisioning")
|
||||
)
|
||||
|
||||
func StartUp(homePath string) error {
|
||||
return datasources.Apply(filepath.Join(homePath, "conf/datasources.yaml"))
|
||||
func StartUp(datasourcePath string) error {
|
||||
return datasources.Provision(datasourcePath)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user