2017-11-30 10:43:28 -06:00
package dashboards
2017-11-23 04:29:06 -06:00
import (
"context"
2017-12-27 09:32:39 -06:00
"errors"
2017-11-23 04:29:06 -06:00
"fmt"
2022-08-10 08:37:51 -05:00
"io"
2017-11-23 04:29:06 -06:00
"os"
"path/filepath"
"strings"
2021-07-21 10:52:41 -05:00
"sync"
2017-11-23 04:29:06 -06:00
"time"
"github.com/grafana/grafana/pkg/components/simplejson"
2019-05-13 01:45:54 -05:00
"github.com/grafana/grafana/pkg/infra/log"
2024-01-25 04:10:35 -06:00
"github.com/grafana/grafana/pkg/infra/metrics"
2022-03-30 08:14:26 -05:00
"github.com/grafana/grafana/pkg/services/accesscontrol"
2020-08-04 11:03:09 -05:00
"github.com/grafana/grafana/pkg/services/dashboards"
2024-01-10 09:48:28 -06:00
"github.com/grafana/grafana/pkg/services/folder"
2022-04-08 06:56:38 -05:00
"github.com/grafana/grafana/pkg/services/provisioning/utils"
2020-08-04 11:03:09 -05:00
"github.com/grafana/grafana/pkg/util"
2017-11-23 04:29:06 -06:00
)
2017-12-27 08:04:32 -06:00
var (
2020-04-15 01:12:52 -05:00
// ErrFolderNameMissing is returned when folder name is missing.
2020-11-05 04:57:20 -06:00
ErrFolderNameMissing = errors . New ( "folder name missing" )
2017-12-27 08:04:32 -06:00
)
2021-01-19 11:57:09 -06:00
// FileReader is responsible for reading dashboards from disk and
2020-04-15 01:12:52 -05:00
// insert/update dashboards to the Grafana database using
2021-01-19 11:57:09 -06:00
// `dashboards.DashboardProvisioningService`.
2020-04-15 01:12:52 -05:00
type FileReader struct {
2020-04-15 22:46:20 -05:00
Cfg * config
2019-04-10 06:29:10 -05:00
Path string
log log . Logger
dashboardProvisioningService dashboards . DashboardProvisioningService
2022-04-08 06:56:38 -05:00
dashboardStore utils . DashboardStore
2020-06-30 08:33:26 -05:00
FoldersFromFilesStructure bool
2024-01-10 09:48:28 -06:00
folderService folder . Service
2021-07-21 10:52:41 -05:00
mux sync . RWMutex
usageTracker * usageTracker
dbWriteAccessRestricted bool
2017-11-23 04:29:06 -06:00
}
2020-04-15 22:46:20 -05:00
// NewDashboardFileReader returns a new filereader based on `config`
2024-01-10 09:48:28 -06:00
func NewDashboardFileReader ( cfg * config , log log . Logger , service dashboards . DashboardProvisioningService ,
dashboardStore utils . DashboardStore , folderService folder . Service ) ( * FileReader , error ) {
2018-01-17 07:33:51 -06:00
var path string
path , ok := cfg . Options [ "path" ] . ( string )
2017-11-23 04:29:06 -06:00
if ! ok {
2018-01-17 07:33:51 -06:00
path , ok = cfg . Options [ "folder" ] . ( string )
if ! ok {
2020-11-05 04:57:20 -06:00
return nil , fmt . Errorf ( "failed to load dashboards, path param is not a string" )
2018-01-17 07:33:51 -06:00
}
log . Warn ( "[Deprecated] The folder property is deprecated. Please use path instead." )
2017-11-23 04:29:06 -06:00
}
2020-06-30 08:33:26 -05:00
foldersFromFilesStructure , _ := cfg . Options [ "foldersFromFilesStructure" ] . ( bool )
if foldersFromFilesStructure && cfg . Folder != "" && cfg . FolderUID != "" {
return nil , fmt . Errorf ( "'folder' and 'folderUID' should be empty using 'foldersFromFilesStructure' option" )
}
2020-04-15 01:12:52 -05:00
return & FileReader {
2019-04-10 06:29:10 -05:00
Cfg : cfg ,
Path : path ,
log : log ,
2022-02-17 10:18:19 -06:00
dashboardProvisioningService : service ,
2022-04-08 06:56:38 -05:00
dashboardStore : dashboardStore ,
2024-01-10 09:48:28 -06:00
folderService : folderService ,
2020-06-30 08:33:26 -05:00
FoldersFromFilesStructure : foldersFromFilesStructure ,
2021-07-21 10:52:41 -05:00
usageTracker : newUsageTracker ( ) ,
2017-11-23 04:29:06 -06:00
} , nil
}
2021-01-19 11:57:09 -06:00
// pollChanges periodically runs walkDisk based on interval specified in the config.
2020-04-15 01:12:52 -05:00
func ( fr * FileReader ) pollChanges ( ctx context . Context ) {
2019-12-12 13:00:56 -06:00
ticker := time . NewTicker ( time . Duration ( int64 ( time . Second ) * fr . Cfg . UpdateIntervalSeconds ) )
2017-11-23 04:29:06 -06:00
for {
select {
2019-12-12 13:00:56 -06:00
case <- ticker . C :
2021-09-14 09:08:04 -05:00
if err := fr . walkDisk ( ctx ) ; err != nil {
2019-04-25 02:06:44 -05:00
fr . log . Error ( "failed to search for dashboards" , "error" , err )
2017-12-05 06:58:56 -06:00
}
2017-11-23 04:29:06 -06:00
case <- ctx . Done ( ) :
2019-04-25 02:06:44 -05:00
return
2017-11-23 04:29:06 -06:00
}
}
}
2021-01-19 11:57:09 -06:00
// walkDisk traverses the file system for the defined path, reading dashboard definition files,
// and applies any change to the database.
2021-09-14 09:08:04 -05:00
func ( fr * FileReader ) walkDisk ( ctx context . Context ) error {
2019-04-25 02:06:44 -05:00
fr . log . Debug ( "Start walking disk" , "path" , fr . Path )
2019-04-30 06:32:18 -05:00
resolvedPath := fr . resolvedPath ( )
2018-09-20 04:46:32 -05:00
if _ , err := os . Stat ( resolvedPath ) ; err != nil {
2019-06-04 15:23:08 -05:00
return err
2017-11-23 04:29:06 -06:00
}
2022-09-21 07:04:01 -05:00
provisionedDashboardRefs , err := getProvisionedDashboardsByPath ( ctx , fr . dashboardProvisioningService , fr . Cfg . Name )
2018-01-24 08:22:03 -06:00
if err != nil {
return err
}
2021-01-19 11:57:09 -06:00
// Find relevant files
2018-02-09 05:17:58 -06:00
filesFoundOnDisk := map [ string ] os . FileInfo { }
2021-01-19 11:57:09 -06:00
if err := filepath . Walk ( resolvedPath , createWalkFn ( filesFoundOnDisk ) ) ; err != nil {
2018-02-09 08:23:38 -06:00
return err
}
2018-01-25 08:25:07 -06:00
2021-11-19 07:32:14 -06:00
fr . handleMissingDashboardFiles ( ctx , provisionedDashboardRefs , filesFoundOnDisk )
2018-02-14 07:45:13 -06:00
2021-07-21 10:52:41 -05:00
usageTracker := newUsageTracker ( )
2020-06-30 08:33:26 -05:00
if fr . FoldersFromFilesStructure {
2021-09-14 09:08:04 -05:00
err = fr . storeDashboardsInFoldersFromFileStructure ( ctx , filesFoundOnDisk , provisionedDashboardRefs , resolvedPath , usageTracker )
2020-06-30 08:33:26 -05:00
} else {
2021-09-14 09:08:04 -05:00
err = fr . storeDashboardsInFolder ( ctx , filesFoundOnDisk , provisionedDashboardRefs , usageTracker )
2020-06-30 08:33:26 -05:00
}
if err != nil {
return err
}
2021-07-21 10:52:41 -05:00
fr . mux . Lock ( )
defer fr . mux . Unlock ( )
2020-06-30 08:33:26 -05:00
2021-07-21 10:52:41 -05:00
fr . usageTracker = usageTracker
2020-06-30 08:33:26 -05:00
return nil
}
2021-07-21 10:52:41 -05:00
func ( fr * FileReader ) changeWritePermissions ( restrict bool ) {
fr . mux . Lock ( )
defer fr . mux . Unlock ( )
fr . dbWriteAccessRestricted = restrict
}
func ( fr * FileReader ) isDatabaseAccessRestricted ( ) bool {
fr . mux . RLock ( )
defer fr . mux . RUnlock ( )
return fr . dbWriteAccessRestricted
}
2020-06-30 08:33:26 -05:00
// storeDashboardsInFolder saves dashboards from the filesystem on disk to the folder from config
2021-09-14 09:08:04 -05:00
func ( fr * FileReader ) storeDashboardsInFolder ( ctx context . Context , filesFoundOnDisk map [ string ] os . FileInfo ,
2023-01-18 06:52:41 -06:00
dashboardRefs map [ string ] * dashboards . DashboardProvisioning , usageTracker * usageTracker ) error {
2023-11-07 03:15:20 -06:00
folderID , folderUID , err := fr . getOrCreateFolder ( ctx , fr . Cfg , fr . dashboardProvisioningService , fr . Cfg . Folder )
2020-11-19 07:47:17 -06:00
if err != nil && ! errors . Is ( err , ErrFolderNameMissing ) {
2020-06-30 08:33:26 -05:00
return err
}
2018-02-14 07:45:13 -06:00
// save dashboards based on json files
for path , fileInfo := range filesFoundOnDisk {
2023-11-07 03:15:20 -06:00
provisioningMetadata , err := fr . saveDashboard ( ctx , path , folderID , folderUID , fileInfo , dashboardRefs )
2018-02-14 07:45:13 -06:00
if err != nil {
2022-09-26 03:57:37 -05:00
fr . log . Error ( "failed to save dashboard" , "file" , path , "error" , err )
2021-01-19 11:57:09 -06:00
continue
2018-02-14 07:45:13 -06:00
}
2021-01-19 11:57:09 -06:00
2021-07-21 10:52:41 -05:00
usageTracker . track ( provisioningMetadata )
2018-02-14 07:45:13 -06:00
}
2020-06-30 08:33:26 -05:00
return nil
}
// storeDashboardsInFoldersFromFilesystemStructure saves dashboards from the filesystem on disk to the same folder
2021-01-19 11:57:09 -06:00
// in Grafana as they are in on the filesystem.
2021-09-14 09:08:04 -05:00
func ( fr * FileReader ) storeDashboardsInFoldersFromFileStructure ( ctx context . Context , filesFoundOnDisk map [ string ] os . FileInfo ,
2023-01-18 06:52:41 -06:00
dashboardRefs map [ string ] * dashboards . DashboardProvisioning , resolvedPath string , usageTracker * usageTracker ) error {
2020-06-30 08:33:26 -05:00
for path , fileInfo := range filesFoundOnDisk {
folderName := ""
dashboardsFolder := filepath . Dir ( path )
if dashboardsFolder != resolvedPath {
folderName = filepath . Base ( dashboardsFolder )
}
2023-11-07 03:15:20 -06:00
folderID , folderUID , err := fr . getOrCreateFolder ( ctx , fr . Cfg , fr . dashboardProvisioningService , folderName )
2020-11-19 07:47:17 -06:00
if err != nil && ! errors . Is ( err , ErrFolderNameMissing ) {
2020-08-04 11:03:09 -05:00
return fmt . Errorf ( "can't provision folder %q from file system structure: %w" , folderName , err )
2020-06-30 08:33:26 -05:00
}
2018-02-14 07:45:13 -06:00
2023-11-07 03:15:20 -06:00
provisioningMetadata , err := fr . saveDashboard ( ctx , path , folderID , folderUID , fileInfo , dashboardRefs )
2021-07-21 10:52:41 -05:00
usageTracker . track ( provisioningMetadata )
2020-06-30 08:33:26 -05:00
if err != nil {
2022-09-26 03:57:37 -05:00
fr . log . Error ( "failed to save dashboard" , "file" , path , "error" , err )
2020-06-30 08:33:26 -05:00
}
}
2018-02-14 07:45:13 -06:00
return nil
}
2019-01-11 07:40:49 -06:00
2019-04-24 01:57:42 -05:00
// handleMissingDashboardFiles will unprovision or delete dashboards which are missing on disk.
2023-01-18 06:52:41 -06:00
func ( fr * FileReader ) handleMissingDashboardFiles ( ctx context . Context , provisionedDashboardRefs map [ string ] * dashboards . DashboardProvisioning ,
2021-01-19 11:57:09 -06:00
filesFoundOnDisk map [ string ] os . FileInfo ) {
2018-02-09 05:17:58 -06:00
// find dashboards to delete since json file is missing
2021-01-19 11:57:09 -06:00
var dashboardsToDelete [ ] int64
2018-02-09 05:17:58 -06:00
for path , provisioningData := range provisionedDashboardRefs {
2018-02-12 01:22:34 -06:00
_ , existsOnDisk := filesFoundOnDisk [ path ]
if ! existsOnDisk {
2023-01-18 06:52:41 -06:00
dashboardsToDelete = append ( dashboardsToDelete , provisioningData . DashboardID )
2018-01-25 08:25:07 -06:00
}
}
2019-04-10 06:29:10 -05:00
if fr . Cfg . DisableDeletion {
// If deletion is disabled for the provisioner we just remove provisioning metadata about the dashboard
// so afterwards the dashboard is considered unprovisioned.
2021-01-19 11:57:09 -06:00
for _ , dashboardID := range dashboardsToDelete {
2020-04-15 01:12:52 -05:00
fr . log . Debug ( "unprovisioning provisioned dashboard. missing on disk" , "id" , dashboardID )
2021-12-02 11:08:59 -06:00
err := fr . dashboardProvisioningService . UnprovisionDashboard ( ctx , dashboardID )
2019-04-10 06:29:10 -05:00
if err != nil {
2020-04-15 01:12:52 -05:00
fr . log . Error ( "failed to unprovision dashboard" , "dashboard_id" , dashboardID , "error" , err )
2019-04-10 06:29:10 -05:00
}
}
} else {
2021-01-19 11:57:09 -06:00
// delete dashboards missing JSON file
for _ , dashboardID := range dashboardsToDelete {
fr . log . Debug ( "deleting provisioned dashboard, missing on disk" , "id" , dashboardID )
2021-11-19 07:32:14 -06:00
err := fr . dashboardProvisioningService . DeleteProvisionedDashboard ( ctx , dashboardID , fr . Cfg . OrgID )
2019-04-10 06:29:10 -05:00
if err != nil {
2020-04-15 01:12:52 -05:00
fr . log . Error ( "failed to delete dashboard" , "id" , dashboardID , "error" , err )
2019-04-10 06:29:10 -05:00
}
2018-01-25 08:25:07 -06:00
}
}
2018-01-24 08:22:03 -06:00
}
2019-04-24 01:57:42 -05:00
// saveDashboard saves or updates the dashboard provisioning file at path.
2023-11-07 03:15:20 -06:00
func ( fr * FileReader ) saveDashboard ( ctx context . Context , path string , folderID int64 , folderUID string , fileInfo os . FileInfo ,
2023-01-18 06:52:41 -06:00
provisionedDashboardRefs map [ string ] * dashboards . DashboardProvisioning ) ( provisioningMetadata , error ) {
2018-02-13 08:47:02 -06:00
provisioningMetadata := provisioningMetadata { }
2018-02-09 05:17:58 -06:00
resolvedFileInfo , err := resolveSymlink ( fileInfo , path )
if err != nil {
2018-02-13 08:47:02 -06:00
return provisioningMetadata , err
2018-02-09 05:17:58 -06:00
}
2018-02-09 08:43:58 -06:00
provisionedData , alreadyProvisioned := provisionedDashboardRefs [ path ]
2018-02-09 05:17:58 -06:00
2023-11-07 03:15:20 -06:00
jsonFile , err := fr . readDashboardFromFile ( path , resolvedFileInfo . ModTime ( ) , folderID , folderUID )
2018-02-09 05:17:58 -06:00
if err != nil {
fr . log . Error ( "failed to load dashboard from " , "file" , path , "error" , err )
2018-02-13 08:47:02 -06:00
return provisioningMetadata , nil
}
2021-04-13 10:02:25 -05:00
upToDate := alreadyProvisioned
if provisionedData != nil {
upToDate = jsonFile . checkSum == provisionedData . CheckSum
2018-05-31 07:13:34 -05:00
}
2021-01-19 11:57:09 -06:00
// keeps track of which UIDs and titles we have already provisioned
2018-05-31 07:13:34 -05:00
dash := jsonFile . dashboard
2023-01-16 09:33:55 -06:00
provisioningMetadata . uid = dash . Dashboard . UID
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Provisioning ) . Inc ( )
2023-11-09 09:57:33 -06:00
// nolint:staticcheck
2023-01-16 09:33:55 -06:00
provisioningMetadata . identity = dashboardIdentity { title : dash . Dashboard . Title , folderID : dash . Dashboard . FolderID }
2018-02-13 08:47:02 -06:00
2023-11-07 03:15:20 -06:00
// fix empty folder_uid from already provisioned dashboards
if upToDate && folderUID != "" {
2024-02-02 03:55:29 -06:00
// search for root dashboard with the specified uid or title
2023-11-07 03:15:20 -06:00
d , err := fr . dashboardStore . GetDashboard (
ctx ,
& dashboards . GetDashboardQuery {
2024-02-02 03:55:29 -06:00
OrgID : jsonFile . dashboard . OrgID ,
UID : jsonFile . dashboard . Dashboard . UID ,
Title : & jsonFile . dashboard . Dashboard . Title ,
FolderUID : util . Pointer ( "" ) ,
2023-11-07 03:15:20 -06:00
} ,
)
if err != nil {
2024-02-02 03:55:29 -06:00
// if no problematic entry is found it's safe to ignore
if ! errors . Is ( err , dashboards . ErrDashboardNotFound ) {
return provisioningMetadata , err
}
} else {
// inconsistency is detected so force updating the dashboard
if d . FolderUID != folderUID {
upToDate = false
}
2023-11-07 03:15:20 -06:00
}
}
2018-02-13 08:47:02 -06:00
if upToDate {
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Provisioning ) . Inc ( )
2023-11-15 09:28:50 -06:00
// nolint:staticcheck
2023-11-07 03:15:20 -06:00
fr . log . Debug ( "provisioned dashboard is up to date" , "provisioner" , fr . Cfg . Name , "file" , path , "folderId" , dash . Dashboard . FolderID , "folderUid" , dash . Dashboard . FolderUID )
2018-02-13 08:47:02 -06:00
return provisioningMetadata , nil
2018-02-09 05:17:58 -06:00
}
2023-01-16 09:33:55 -06:00
if dash . Dashboard . ID != 0 {
2018-03-26 06:17:39 -05:00
dash . Dashboard . Data . Set ( "id" , nil )
2023-01-16 09:33:55 -06:00
dash . Dashboard . ID = 0
2018-02-09 08:43:58 -06:00
}
if alreadyProvisioned {
2023-01-18 06:52:41 -06:00
dash . Dashboard . SetID ( provisionedData . DashboardID )
2018-02-09 05:17:58 -06:00
}
2021-07-21 10:52:41 -05:00
if ! fr . isDatabaseAccessRestricted ( ) {
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Provisioning ) . Inc ( )
2023-11-15 09:28:50 -06:00
// nolint:staticcheck
2023-11-07 03:15:20 -06:00
fr . log . Debug ( "saving new dashboard" , "provisioner" , fr . Cfg . Name , "file" , path , "folderId" , dash . Dashboard . FolderID , "folderUid" , dash . Dashboard . FolderUID )
2023-01-16 09:33:55 -06:00
dp := & dashboards . DashboardProvisioning {
ExternalID : path ,
2021-07-21 10:52:41 -05:00
Name : fr . Cfg . Name ,
Updated : resolvedFileInfo . ModTime ( ) . Unix ( ) ,
CheckSum : jsonFile . checkSum ,
}
2022-03-10 05:58:18 -06:00
_ , err := fr . dashboardProvisioningService . SaveProvisionedDashboard ( ctx , dash , dp )
2022-03-03 08:05:47 -06:00
if err != nil {
2021-07-21 10:52:41 -05:00
return provisioningMetadata , err
}
} else {
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Provisioning ) . Inc ( )
2023-11-15 09:28:50 -06:00
// nolint:staticcheck
2021-07-21 10:52:41 -05:00
fr . log . Warn ( "Not saving new dashboard due to restricted database access" , "provisioner" , fr . Cfg . Name ,
2023-01-16 09:33:55 -06:00
"file" , path , "folderId" , dash . Dashboard . FolderID )
2018-05-31 07:13:34 -05:00
}
2021-07-21 10:52:41 -05:00
return provisioningMetadata , nil
2018-02-09 05:17:58 -06:00
}
2022-09-21 07:04:01 -05:00
func getProvisionedDashboardsByPath ( ctx context . Context , service dashboards . DashboardProvisioningService , name string ) (
2023-01-18 06:52:41 -06:00
map [ string ] * dashboards . DashboardProvisioning , error ) {
2022-09-21 07:04:01 -05:00
arr , err := service . GetProvisionedDashboardData ( ctx , name )
2018-01-24 08:22:03 -06:00
if err != nil {
return nil , err
}
2023-01-18 06:52:41 -06:00
byPath := map [ string ] * dashboards . DashboardProvisioning { }
2018-01-24 08:22:03 -06:00
for _ , pd := range arr {
2023-01-18 06:52:41 -06:00
byPath [ pd . ExternalID ] = pd
2018-01-24 08:22:03 -06:00
}
return byPath , nil
2017-12-27 08:04:32 -06:00
}
2023-11-07 03:15:20 -06:00
func ( fr * FileReader ) getOrCreateFolder ( ctx context . Context , cfg * config , service dashboards . DashboardProvisioningService , folderName string ) ( int64 , string , error ) {
2020-06-30 08:33:26 -05:00
if folderName == "" {
2023-11-07 03:15:20 -06:00
return 0 , "" , ErrFolderNameMissing
2017-12-27 09:32:39 -06:00
}
2024-05-31 03:09:20 -05:00
// TODO use folder service instead
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Provisioning ) . Inc ( )
2023-03-28 06:24:19 -05:00
cmd := & dashboards . GetDashboardQuery {
2023-11-09 09:53:39 -06:00
FolderID : util . Pointer ( int64 ( 0 ) ) , // nolint:staticcheck
2023-03-28 06:24:19 -05:00
OrgID : cfg . OrgID ,
}
2024-05-07 14:38:53 -05:00
if cfg . FolderUID != "" {
cmd . UID = cfg . FolderUID
} else {
cmd . Title = & folderName
}
2023-01-25 03:36:26 -06:00
result , err := fr . dashboardStore . GetDashboard ( ctx , cmd )
2017-12-27 09:32:39 -06:00
2022-06-30 08:31:54 -05:00
if err != nil && ! errors . Is ( err , dashboards . ErrDashboardNotFound ) {
2023-11-07 03:15:20 -06:00
return 0 , "" , err
2017-12-27 09:32:39 -06:00
}
// dashboard folder not found. create one.
2022-06-30 08:31:54 -05:00
if errors . Is ( err , dashboards . ErrDashboardNotFound ) {
2019-04-24 01:57:42 -05:00
// set dashboard folderUid if given
2022-03-30 08:14:26 -05:00
if cfg . FolderUID == accesscontrol . GeneralFolderUID {
2023-11-07 03:15:20 -06:00
return 0 , "" , dashboards . ErrFolderInvalidUID
2022-03-30 08:14:26 -05:00
}
2024-01-10 09:48:28 -06:00
createCmd := & folder . CreateFolderCommand {
OrgID : cfg . OrgID ,
UID : cfg . FolderUID ,
Title : folderName ,
}
f , err := service . SaveFolderForProvisionedDashboards ( ctx , createCmd )
2017-12-27 09:32:39 -06:00
if err != nil {
2023-11-07 03:15:20 -06:00
return 0 , "" , err
2017-12-27 09:32:39 -06:00
}
2024-01-25 04:10:35 -06:00
metrics . MFolderIDsServiceCount . WithLabelValues ( metrics . Provisioning ) . Inc ( )
2024-01-10 09:48:28 -06:00
// nolint:staticcheck
return f . ID , f . UID , nil
2017-12-27 09:32:39 -06:00
}
2023-01-25 03:36:26 -06:00
if ! result . IsFolder {
2023-11-07 03:15:20 -06:00
return 0 , "" , fmt . Errorf ( "got invalid response. expected folder, found dashboard" )
2017-12-27 09:32:39 -06:00
}
2023-11-07 03:15:20 -06:00
return result . ID , result . UID , nil
2017-12-27 09:32:39 -06:00
}
2018-01-18 05:04:12 -06:00
func resolveSymlink ( fileinfo os . FileInfo , path string ) ( os . FileInfo , error ) {
checkFilepath , err := filepath . EvalSymlinks ( path )
if path != checkFilepath {
fi , err := os . Lstat ( checkFilepath )
if err != nil {
return nil , err
}
return fi , nil
}
return fileinfo , err
}
2018-02-09 05:17:58 -06:00
func createWalkFn ( filesOnDisk map [ string ] os . FileInfo ) filepath . WalkFunc {
2017-12-27 08:04:32 -06:00
return func ( path string , fileInfo os . FileInfo , err error ) error {
2017-11-23 04:29:06 -06:00
if err != nil {
return err
}
2018-01-18 05:04:12 -06:00
isValid , err := validateWalkablePath ( fileInfo )
if ! isValid {
return err
2017-11-23 04:29:06 -06:00
}
2018-02-09 05:17:58 -06:00
filesOnDisk [ path ] = fileInfo
return nil
2017-12-27 08:04:32 -06:00
}
2017-11-23 04:29:06 -06:00
}
2018-01-24 08:22:03 -06:00
2018-01-18 05:04:12 -06:00
func validateWalkablePath ( fileInfo os . FileInfo ) ( bool , error ) {
if fileInfo . IsDir ( ) {
if strings . HasPrefix ( fileInfo . Name ( ) , "." ) {
return false , filepath . SkipDir
}
return false , nil
}
if ! strings . HasSuffix ( fileInfo . Name ( ) , ".json" ) {
return false , nil
}
return true , nil
}
2020-04-15 01:12:52 -05:00
type dashboardJSONFile struct {
2018-05-31 07:13:34 -05:00
dashboard * dashboards . SaveDashboardDTO
checkSum string
lastModified time . Time
}
2023-11-07 03:15:20 -06:00
func ( fr * FileReader ) readDashboardFromFile ( path string , lastModified time . Time , folderID int64 , folderUID string ) ( * dashboardJSONFile , error ) {
2020-12-03 15:13:06 -06:00
// nolint:gosec
// We can ignore the gosec G304 warning on this one because `path` comes from the provisioning configuration file.
2017-11-23 04:29:06 -06:00
reader , err := os . Open ( path )
if err != nil {
return nil , err
}
2020-12-03 03:11:14 -06:00
defer func ( ) {
if err := reader . Close ( ) ; err != nil {
fr . log . Warn ( "Failed to close file" , "path" , path , "err" , err )
}
} ( )
2017-11-23 04:29:06 -06:00
2022-08-10 08:37:51 -05:00
all , err := io . ReadAll ( reader )
2018-05-31 07:13:34 -05:00
if err != nil {
return nil , err
}
checkSum , err := util . Md5SumString ( string ( all ) )
if err != nil {
return nil , err
}
data , err := simplejson . NewJson ( all )
2017-11-23 04:29:06 -06:00
if err != nil {
return nil , err
}
2023-11-07 03:15:20 -06:00
dash , err := createDashboardJSON ( data , lastModified , fr . Cfg , folderID , folderUID )
2017-11-28 09:57:14 -06:00
if err != nil {
return nil , err
}
2017-11-23 04:29:06 -06:00
2020-04-15 01:12:52 -05:00
return & dashboardJSONFile {
2018-05-31 07:13:34 -05:00
dashboard : dash ,
checkSum : checkSum ,
lastModified : lastModified ,
} , nil
2017-11-23 04:29:06 -06:00
}
2018-02-13 08:47:02 -06:00
2020-04-15 01:12:52 -05:00
func ( fr * FileReader ) resolvedPath ( ) string {
2019-04-30 06:32:18 -05:00
if _ , err := os . Stat ( fr . Path ) ; os . IsNotExist ( err ) {
2018-09-20 04:46:32 -05:00
fr . log . Error ( "Cannot read directory" , "error" , err )
}
2019-04-30 06:32:18 -05:00
path , err := filepath . Abs ( fr . Path )
2018-09-20 04:46:32 -05:00
if err != nil {
2019-04-30 06:32:18 -05:00
fr . log . Error ( "Could not create absolute path" , "path" , fr . Path , "error" , err )
2018-09-20 04:46:32 -05:00
}
path , err = filepath . EvalSymlinks ( path )
if err != nil {
2019-04-30 06:32:18 -05:00
fr . log . Error ( "Failed to read content of symlinked path" , "path" , fr . Path , "error" , err )
2018-09-20 04:46:32 -05:00
}
if path == "" {
2019-04-30 06:32:18 -05:00
path = fr . Path
2018-09-20 04:46:32 -05:00
fr . log . Info ( "falling back to original path due to EvalSymlink/Abs failure" )
}
return path
}
2021-07-21 10:52:41 -05:00
func ( fr * FileReader ) getUsageTracker ( ) * usageTracker {
fr . mux . RLock ( )
defer fr . mux . RUnlock ( )
return fr . usageTracker
}
2018-02-13 08:47:02 -06:00
type provisioningMetadata struct {
2020-06-30 08:33:26 -05:00
uid string
identity dashboardIdentity
}
type dashboardIdentity struct {
2023-12-13 03:50:46 -06:00
// Deprecated: use folderUID instead
folderID int64
folderUID string
title string
2020-06-30 08:33:26 -05:00
}
func ( d * dashboardIdentity ) Exists ( ) bool {
2021-07-21 10:52:41 -05:00
return len ( d . title ) > 0
2018-02-13 08:47:02 -06:00
}
2021-07-21 10:52:41 -05:00
func newUsageTracker ( ) * usageTracker {
return & usageTracker {
uidUsage : map [ string ] uint8 { } ,
titleUsage : map [ dashboardIdentity ] uint8 { } ,
2020-06-30 08:33:26 -05:00
}
2018-02-13 08:47:02 -06:00
}
2021-07-21 10:52:41 -05:00
type usageTracker struct {
uidUsage map [ string ] uint8
titleUsage map [ dashboardIdentity ] uint8
2018-02-13 08:47:02 -06:00
}
2021-07-21 10:52:41 -05:00
func ( t * usageTracker ) track ( pm provisioningMetadata ) {
2018-02-13 08:47:02 -06:00
if len ( pm . uid ) > 0 {
2021-07-21 10:52:41 -05:00
t . uidUsage [ pm . uid ] ++
2018-02-13 08:47:02 -06:00
}
2020-06-30 08:33:26 -05:00
if pm . identity . Exists ( ) {
2021-07-21 10:52:41 -05:00
t . titleUsage [ pm . identity ] ++
2018-02-13 08:47:02 -06:00
}
}