mirror of https://github.com/grafana/grafana.git
provisioning: Warns the user when uid or title is re-used. (#10892)
* provisioning: Warns the user when uid or title is re-used. Closes #10880
This commit is contained in:
parent
d31e333e7e
commit
ba0285a3ec
|
|
@ -155,7 +155,7 @@ Since not all datasources have the same configuration settings we only have the
|
|||
|
||||
#### Secure Json data
|
||||
|
||||
{"authType":"keys","defaultRegion":"us-west-2","timeField":"@timestamp"}
|
||||
`{"authType":"keys","defaultRegion":"us-west-2","timeField":"@timestamp"}`
|
||||
|
||||
Secure json data is a map of settings that will be encrypted with [secret key](/installation/configuration/#secret-key) from the Grafana config. The purpose of this is only to hide content from the users of the application. This should be used for storing TLS Cert and password that Grafana will append to the request on the server side. All of these settings are optional.
|
||||
|
||||
|
|
@ -169,7 +169,7 @@ Secure json data is a map of settings that will be encrypted with [secret key](/
|
|||
|
||||
### Dashboards
|
||||
|
||||
It's possible to manage dashboards in Grafana by adding one or more yaml config files in the [`provisioning/dashboards`](/installation/configuration/#provisioning) directory. Each config file can contain a list of `dashboards providers` that will load dashboards into Grafana. Currently we only support reading dashboards from file but we will add more providers in the future.
|
||||
It's possible to manage dashboards in Grafana by adding one or more yaml config files in the [`provisioning/dashboards`](/installation/configuration/#provisioning) directory. Each config file can contain a list of `dashboards providers` that will load dashboards into Grafana from the local filesystem.
|
||||
|
||||
The dashboard provider config file looks somewhat like this:
|
||||
|
||||
|
|
@ -183,3 +183,8 @@ The dashboard provider config file looks somewhat like this:
|
|||
```
|
||||
|
||||
When Grafana starts, it will update/insert all dashboards available in the configured folders. If you modify the file, the dashboard will also be updated.
|
||||
|
||||
> **Note.** Provisioning allows you to overwrite existing dashboards
|
||||
> which leads to problems if you re-use settings that are supposed to be unique.
|
||||
> Be careful not to re-use the same `title` multiple times within a folder
|
||||
> or `uid` within the same installation as this will cause weird behaviours.
|
||||
|
|
|
|||
|
|
@ -124,37 +124,48 @@ func (fr *fileReader) startWalkingDisk() error {
|
|||
}
|
||||
}
|
||||
|
||||
sanityChecker := newProvisioningSanityChecker(fr.Cfg.Name)
|
||||
|
||||
// save dashboards based on json files
|
||||
for path, fileInfo := range filesFoundOnDisk {
|
||||
err = fr.saveDashboard(path, folderId, fileInfo, provisionedDashboardRefs)
|
||||
provisioningMetadata, err := fr.saveDashboard(path, folderId, fileInfo, provisionedDashboardRefs)
|
||||
sanityChecker.track(provisioningMetadata)
|
||||
if err != nil {
|
||||
fr.log.Error("failed to save dashboard", "error", err)
|
||||
}
|
||||
}
|
||||
sanityChecker.logWarnings(fr.log)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.FileInfo, provisionedDashboardRefs map[string]*models.DashboardProvisioning) error {
|
||||
func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.FileInfo, provisionedDashboardRefs map[string]*models.DashboardProvisioning) (provisioningMetadata, error) {
|
||||
provisioningMetadata := provisioningMetadata{}
|
||||
resolvedFileInfo, err := resolveSymlink(fileInfo, path)
|
||||
if err != nil {
|
||||
return err
|
||||
return provisioningMetadata, err
|
||||
}
|
||||
|
||||
provisionedData, alreadyProvisioned := provisionedDashboardRefs[path]
|
||||
if alreadyProvisioned && provisionedData.Updated.Unix() == resolvedFileInfo.ModTime().Unix() {
|
||||
return nil // dashboard is already in sync with the database
|
||||
}
|
||||
upToDate := alreadyProvisioned && provisionedData.Updated.Unix() == resolvedFileInfo.ModTime().Unix()
|
||||
|
||||
dash, err := fr.readDashboardFromFile(path, resolvedFileInfo.ModTime(), folderId)
|
||||
if err != nil {
|
||||
fr.log.Error("failed to load dashboard from ", "file", path, "error", err)
|
||||
return nil
|
||||
return provisioningMetadata, nil
|
||||
}
|
||||
|
||||
// keeps track of what uid's and title's we have already provisioned
|
||||
provisioningMetadata.uid = dash.Dashboard.Uid
|
||||
provisioningMetadata.title = dash.Dashboard.Title
|
||||
|
||||
if upToDate {
|
||||
return provisioningMetadata, nil
|
||||
}
|
||||
|
||||
if dash.Dashboard.Id != 0 {
|
||||
fr.log.Error("provisioned dashboard json files cannot contain id")
|
||||
return nil
|
||||
return provisioningMetadata, nil
|
||||
}
|
||||
|
||||
if alreadyProvisioned {
|
||||
|
|
@ -164,7 +175,7 @@ func (fr *fileReader) saveDashboard(path string, folderId int64, fileInfo os.Fil
|
|||
fr.log.Debug("saving new dashboard", "file", path)
|
||||
dp := &models.DashboardProvisioning{ExternalId: path, Name: fr.Cfg.Name, Updated: resolvedFileInfo.ModTime()}
|
||||
_, err = fr.dashboardRepo.SaveProvisionedDashboard(dash, dp)
|
||||
return err
|
||||
return provisioningMetadata, err
|
||||
}
|
||||
|
||||
func getProvisionedDashboardByPath(repo dashboards.Repository, name string) (map[string]*models.DashboardProvisioning, error) {
|
||||
|
|
@ -280,3 +291,46 @@ func (fr *fileReader) readDashboardFromFile(path string, lastModified time.Time,
|
|||
|
||||
return dash, nil
|
||||
}
|
||||
|
||||
type provisioningMetadata struct {
|
||||
uid string
|
||||
title string
|
||||
}
|
||||
|
||||
func newProvisioningSanityChecker(provisioningProvider string) provisioningSanityChecker {
|
||||
return provisioningSanityChecker{
|
||||
provisioningProvider: provisioningProvider,
|
||||
uidUsage: map[string]uint8{},
|
||||
titleUsage: map[string]uint8{}}
|
||||
}
|
||||
|
||||
type provisioningSanityChecker struct {
|
||||
provisioningProvider string
|
||||
uidUsage map[string]uint8
|
||||
titleUsage map[string]uint8
|
||||
}
|
||||
|
||||
func (checker provisioningSanityChecker) track(pm provisioningMetadata) {
|
||||
if len(pm.uid) > 0 {
|
||||
checker.uidUsage[pm.uid] += 1
|
||||
}
|
||||
if len(pm.title) > 0 {
|
||||
checker.titleUsage[pm.title] += 1
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (checker provisioningSanityChecker) logWarnings(log log.Logger) {
|
||||
for uid, times := range checker.uidUsage {
|
||||
if times > 1 {
|
||||
log.Error("the same 'uid' is used more than once", "uid", uid, "provider", checker.provisioningProvider)
|
||||
}
|
||||
}
|
||||
|
||||
for title, times := range checker.titleUsage {
|
||||
if times > 1 {
|
||||
log.Error("the same 'title' is used more than once", "title", title, "provider", checker.provisioningProvider)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue