2017-11-30 10:43:28 -06:00
|
|
|
package dashboards
|
2017-11-23 04:29:06 -06:00
|
|
|
|
|
|
|
import (
|
2021-11-03 05:31:56 -05:00
|
|
|
"context"
|
2019-01-11 07:40:49 -06:00
|
|
|
"fmt"
|
2022-08-11 06:21:12 -05:00
|
|
|
"io/fs"
|
2018-02-13 07:29:56 -06:00
|
|
|
"os"
|
2017-11-23 04:29:06 -06:00
|
|
|
"path/filepath"
|
|
|
|
"strings"
|
|
|
|
|
2019-05-13 01:45:54 -05:00
|
|
|
"github.com/grafana/grafana/pkg/infra/log"
|
2022-10-13 07:40:46 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/org"
|
2020-07-30 04:59:12 -05:00
|
|
|
"github.com/grafana/grafana/pkg/services/provisioning/utils"
|
2022-12-06 14:17:17 -06:00
|
|
|
"gopkg.in/yaml.v3"
|
2017-11-23 04:29:06 -06:00
|
|
|
)
|
|
|
|
|
|
|
|
type configReader struct {
|
2022-10-13 07:40:46 -05:00
|
|
|
path string
|
|
|
|
log log.Logger
|
|
|
|
orgService org.Service
|
2017-11-23 04:29:06 -06:00
|
|
|
}
|
|
|
|
|
2022-08-11 06:21:12 -05:00
|
|
|
func (cr *configReader) parseConfigs(file fs.DirEntry) ([]*config, error) {
|
2018-02-13 07:29:56 -06:00
|
|
|
filename, _ := filepath.Abs(filepath.Join(cr.path, file.Name()))
|
2020-12-03 15:13:06 -06:00
|
|
|
|
|
|
|
// nolint:gosec
|
|
|
|
// We can ignore the gosec G304 warning on this one because `filename` comes from ps.Cfg.ProvisioningPath
|
2022-08-10 08:37:51 -05:00
|
|
|
yamlFile, err := os.ReadFile(filename)
|
2018-02-13 07:29:56 -06:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
2020-04-15 22:46:20 -05:00
|
|
|
apiVersion := &configVersion{APIVersion: 0}
|
2018-02-13 07:14:10 -06:00
|
|
|
|
2019-04-24 01:57:42 -05:00
|
|
|
// We ignore the error here because it errors out for version 0 which does not have apiVersion
|
|
|
|
// specified (so 0 is default). This can also error in case the apiVersion is not an integer but at the moment
|
|
|
|
// this does not handle that case and would still go on as if version = 0.
|
|
|
|
// TODO: return appropriate error in case the apiVersion is specified but isn't integer (or even if it is
|
|
|
|
// integer > max version?).
|
|
|
|
_ = yaml.Unmarshal(yamlFile, &apiVersion)
|
2018-02-13 07:14:10 -06:00
|
|
|
|
2020-04-15 22:46:20 -05:00
|
|
|
if apiVersion.APIVersion > 0 {
|
|
|
|
v1 := &configV1{}
|
2018-02-13 07:14:10 -06:00
|
|
|
err := yaml.Unmarshal(yamlFile, &v1)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if v1 != nil {
|
2020-04-14 13:54:00 -05:00
|
|
|
return v1.mapToDashboardsAsConfig()
|
2018-02-13 07:14:10 -06:00
|
|
|
}
|
|
|
|
} else {
|
2020-04-15 22:46:20 -05:00
|
|
|
var v0 []*configV0
|
2018-02-13 07:14:10 -06:00
|
|
|
err := yaml.Unmarshal(yamlFile, &v0)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
if v0 != nil {
|
2018-02-13 07:29:56 -06:00
|
|
|
cr.log.Warn("[Deprecated] the dashboard provisioning config is outdated. please upgrade", "filename", filename)
|
2020-04-14 13:54:00 -05:00
|
|
|
return mapV0ToDashboardsAsConfig(v0)
|
2018-02-13 07:14:10 -06:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-04-15 22:46:20 -05:00
|
|
|
return []*config{}, nil
|
2018-02-13 07:14:10 -06:00
|
|
|
}
|
|
|
|
|
2021-11-03 05:31:56 -05:00
|
|
|
func (cr *configReader) readConfig(ctx context.Context) ([]*config, error) {
|
2020-04-15 22:46:20 -05:00
|
|
|
var dashboards []*config
|
2018-01-24 07:20:16 -06:00
|
|
|
|
2022-08-11 06:21:12 -05:00
|
|
|
files, err := os.ReadDir(cr.path)
|
2017-11-23 04:29:06 -06:00
|
|
|
if err != nil {
|
2019-02-07 08:43:05 -06:00
|
|
|
cr.log.Error("can't read dashboard provisioning files from directory", "path", cr.path, "error", err)
|
2018-01-24 07:20:16 -06:00
|
|
|
return dashboards, nil
|
2017-11-23 04:29:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
for _, file := range files {
|
2018-02-20 00:33:24 -06:00
|
|
|
if !strings.HasSuffix(file.Name(), ".yaml") && !strings.HasSuffix(file.Name(), ".yml") {
|
2017-11-23 04:29:06 -06:00
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2018-02-13 07:29:56 -06:00
|
|
|
parsedDashboards, err := cr.parseConfigs(file)
|
2017-11-23 04:29:06 -06:00
|
|
|
if err != nil {
|
2019-01-11 07:40:49 -06:00
|
|
|
return nil, fmt.Errorf("could not parse provisioning config file: %s error: %v", file.Name(), err)
|
2017-11-23 04:29:06 -06:00
|
|
|
}
|
|
|
|
|
2018-02-13 07:14:10 -06:00
|
|
|
if len(parsedDashboards) > 0 {
|
|
|
|
dashboards = append(dashboards, parsedDashboards...)
|
|
|
|
}
|
2017-11-23 04:29:06 -06:00
|
|
|
}
|
|
|
|
|
2019-04-24 01:57:42 -05:00
|
|
|
uidUsage := map[string]uint8{}
|
|
|
|
for _, dashboard := range dashboards {
|
2020-04-15 22:46:20 -05:00
|
|
|
if dashboard.OrgID == 0 {
|
|
|
|
dashboard.OrgID = 1
|
2017-11-23 04:29:06 -06:00
|
|
|
}
|
2018-05-31 12:35:46 -05:00
|
|
|
|
2022-10-13 07:40:46 -05:00
|
|
|
if err := utils.CheckOrgExists(ctx, cr.orgService, dashboard.OrgID); err != nil {
|
2020-07-30 04:59:12 -05:00
|
|
|
return nil, fmt.Errorf("failed to provision dashboards with %q reader: %w", dashboard.Name, err)
|
|
|
|
}
|
|
|
|
|
2020-05-19 08:01:04 -05:00
|
|
|
if dashboard.Type == "" {
|
|
|
|
dashboard.Type = "file"
|
|
|
|
}
|
|
|
|
|
2019-04-24 01:57:42 -05:00
|
|
|
if dashboard.UpdateIntervalSeconds == 0 {
|
|
|
|
dashboard.UpdateIntervalSeconds = 10
|
|
|
|
}
|
2020-04-15 22:46:20 -05:00
|
|
|
if len(dashboard.FolderUID) > 0 {
|
|
|
|
uidUsage[dashboard.FolderUID]++
|
2019-04-24 01:57:42 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for uid, times := range uidUsage {
|
|
|
|
if times > 1 {
|
2021-01-19 11:57:09 -06:00
|
|
|
cr.log.Error("the same folder UID is used more than once", "folderUid", uid)
|
2018-05-31 12:35:46 -05:00
|
|
|
}
|
2017-11-23 04:29:06 -06:00
|
|
|
}
|
|
|
|
|
|
|
|
return dashboards, nil
|
|
|
|
}
|