2018-02-12 15:17:32 +01:00
|
|
|
package datasources
|
|
|
|
|
|
|
|
|
|
import (
|
2020-07-30 13:59:12 +04:00
|
|
|
"fmt"
|
2018-02-12 15:17:32 +01:00
|
|
|
"io/ioutil"
|
2018-02-13 14:29:56 +01:00
|
|
|
"os"
|
2018-02-12 15:17:32 +01:00
|
|
|
"path/filepath"
|
|
|
|
|
"strings"
|
|
|
|
|
|
2019-05-13 14:45:54 +08:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2020-07-20 12:01:25 +04:00
|
|
|
"github.com/grafana/grafana/pkg/models"
|
2020-07-30 13:59:12 +04:00
|
|
|
"github.com/grafana/grafana/pkg/services/provisioning/utils"
|
2018-02-12 15:17:32 +01:00
|
|
|
"gopkg.in/yaml.v2"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
type configReader struct {
|
|
|
|
|
log log.Logger
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 21:07:07 +02:00
|
|
|
func (cr *configReader) readConfig(path string) ([]*configs, error) {
|
|
|
|
|
var datasources []*configs
|
2018-02-12 15:17:32 +01:00
|
|
|
|
|
|
|
|
files, err := ioutil.ReadDir(path)
|
|
|
|
|
if err != nil {
|
2019-02-07 15:43:05 +01:00
|
|
|
cr.log.Error("can't read datasource provisioning files from directory", "path", path, "error", err)
|
2018-02-12 15:17:32 +01:00
|
|
|
return datasources, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, file := range files {
|
2018-02-20 07:33:24 +01:00
|
|
|
if strings.HasSuffix(file.Name(), ".yaml") || strings.HasSuffix(file.Name(), ".yml") {
|
2018-02-13 14:29:56 +01:00
|
|
|
datasource, err := cr.parseDatasourceConfig(path, file)
|
2018-02-12 15:17:32 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if datasource != nil {
|
|
|
|
|
datasources = append(datasources, datasource)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-07-20 12:01:25 +04:00
|
|
|
err = cr.validateDefaultUniqueness(datasources)
|
2018-02-12 15:17:32 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return datasources, nil
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 21:07:07 +02:00
|
|
|
func (cr *configReader) parseDatasourceConfig(path string, file os.FileInfo) (*configs, error) {
|
2018-02-13 14:29:56 +01:00
|
|
|
filename, _ := filepath.Abs(filepath.Join(path, file.Name()))
|
2020-12-03 22:13:06 +01:00
|
|
|
|
|
|
|
|
// nolint:gosec
|
|
|
|
|
// We can ignore the gosec G304 warning on this one because `filename` comes from ps.Cfg.ProvisioningPath
|
2018-02-13 14:29:56 +01:00
|
|
|
yamlFile, err := ioutil.ReadFile(filename)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 21:07:07 +02:00
|
|
|
var apiVersion *configVersion
|
2018-02-13 15:02:27 +01:00
|
|
|
err = yaml.Unmarshal(yamlFile, &apiVersion)
|
2018-02-12 15:17:32 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2018-02-14 11:22:24 +01:00
|
|
|
if apiVersion == nil {
|
2020-04-11 21:07:07 +02:00
|
|
|
apiVersion = &configVersion{APIVersion: 0}
|
2018-02-14 11:22:24 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-11 21:07:07 +02:00
|
|
|
if apiVersion.APIVersion > 0 {
|
|
|
|
|
v1 := &configsV1{log: cr.log}
|
2019-04-15 11:11:17 +02:00
|
|
|
err = yaml.Unmarshal(yamlFile, v1)
|
2018-02-12 15:17:32 +01:00
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
|
|
|
|
|
2020-04-11 21:07:07 +02:00
|
|
|
return v1.mapToDatasourceFromConfig(apiVersion.APIVersion), nil
|
2018-02-12 15:17:32 +01:00
|
|
|
}
|
|
|
|
|
|
2020-04-11 21:07:07 +02:00
|
|
|
var v0 *configsV0
|
2018-02-12 15:17:32 +01:00
|
|
|
err = yaml.Unmarshal(yamlFile, &v0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, err
|
|
|
|
|
}
|
2018-02-13 14:29:56 +01:00
|
|
|
|
|
|
|
|
cr.log.Warn("[Deprecated] the datasource provisioning config is outdated. please upgrade", "filename", filename)
|
|
|
|
|
|
2020-04-11 21:07:07 +02:00
|
|
|
return v0.mapToDatasourceFromConfig(apiVersion.APIVersion), nil
|
2018-02-12 15:17:32 +01:00
|
|
|
}
|
|
|
|
|
|
2020-07-20 12:01:25 +04:00
|
|
|
func (cr *configReader) validateDefaultUniqueness(datasources []*configs) error {
|
2018-08-14 14:48:14 +02:00
|
|
|
defaultCount := map[int64]int{}
|
2018-02-12 15:17:32 +01:00
|
|
|
for i := range datasources {
|
|
|
|
|
if datasources[i].Datasources == nil {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, ds := range datasources[i].Datasources {
|
2020-04-11 21:07:07 +02:00
|
|
|
if ds.OrgID == 0 {
|
|
|
|
|
ds.OrgID = 1
|
2018-02-12 15:17:32 +01:00
|
|
|
}
|
|
|
|
|
|
2020-07-30 13:59:12 +04:00
|
|
|
if err := cr.validateAccessAndOrgID(ds); err != nil {
|
|
|
|
|
return fmt.Errorf("failed to provision %q data source: %w", ds.Name, err)
|
2020-05-14 12:40:00 +02:00
|
|
|
}
|
|
|
|
|
|
2018-02-12 15:17:32 +01:00
|
|
|
if ds.IsDefault {
|
2020-07-16 14:39:01 +02:00
|
|
|
defaultCount[ds.OrgID]++
|
2020-04-11 21:07:07 +02:00
|
|
|
if defaultCount[ds.OrgID] > 1 {
|
2018-02-12 15:17:32 +01:00
|
|
|
return ErrInvalidConfigToManyDefault
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, ds := range datasources[i].DeleteDatasources {
|
2020-04-11 21:07:07 +02:00
|
|
|
if ds.OrgID == 0 {
|
|
|
|
|
ds.OrgID = 1
|
2018-02-12 15:17:32 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2020-07-30 13:59:12 +04:00
|
|
|
|
|
|
|
|
func (cr *configReader) validateAccessAndOrgID(ds *upsertDataSourceFromConfig) error {
|
|
|
|
|
if err := utils.CheckOrgExists(ds.OrgID); err != nil {
|
|
|
|
|
return err
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ds.Access == "" {
|
|
|
|
|
ds.Access = models.DS_ACCESS_PROXY
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if ds.Access != models.DS_ACCESS_DIRECT && ds.Access != models.DS_ACCESS_PROXY {
|
|
|
|
|
cr.log.Warn("invalid access value, will use 'proxy' instead", "value", ds.Access)
|
|
|
|
|
ds.Access = models.DS_ACCESS_PROXY
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|