@ -140,14 +140,14 @@ func (fr *FileReader) isDatabaseAccessRestricted() bool {
// storeDashboardsInFolder saves dashboards from the filesystem on disk to the folder from config
func ( fr * FileReader ) storeDashboardsInFolder ( ctx context . Context , filesFoundOnDisk map [ string ] os . FileInfo ,
dashboardRefs map [ string ] * dashboards . DashboardProvisioning , usageTracker * usageTracker ) error {
folderID , err := fr . getOrCreateFolderID ( ctx , fr . Cfg , fr . dashboardProvisioningService , fr . Cfg . Folder )
folderID , folderUID , err := fr . getOrCreateFolder ( ctx , fr . Cfg , fr . dashboardProvisioningService , fr . Cfg . Folder )
if err != nil && ! errors . Is ( err , ErrFolderNameMissing ) {
return err
}
// save dashboards based on json files
for path , fileInfo := range filesFoundOnDisk {
provisioningMetadata , err := fr . saveDashboard ( ctx , path , folderID , fileInfo , dashboardRefs )
provisioningMetadata , err := fr . saveDashboard ( ctx , path , folderID , folderUID , f ileInfo , dashboardRefs )
if err != nil {
fr . log . Error ( "failed to save dashboard" , "file" , path , "error" , err )
continue
@ -170,12 +170,12 @@ func (fr *FileReader) storeDashboardsInFoldersFromFileStructure(ctx context.Cont
folderName = filepath . Base ( dashboardsFolder )
}
folderID , err := fr . getOrCreateFolderID ( ctx , fr . Cfg , fr . dashboardProvisioningService , folderName )
folderID , folderUID , err := fr . getOrCreateFolder ( ctx , fr . Cfg , fr . dashboardProvisioningService , folderName )
if err != nil && ! errors . Is ( err , ErrFolderNameMissing ) {
return fmt . Errorf ( "can't provision folder %q from file system structure: %w" , folderName , err )
}
provisioningMetadata , err := fr . saveDashboard ( ctx , path , folderID , fileInfo , dashboardRefs )
provisioningMetadata , err := fr . saveDashboard ( ctx , path , folderID , folderUID , f ileInfo , dashboardRefs )
usageTracker . track ( provisioningMetadata )
if err != nil {
fr . log . Error ( "failed to save dashboard" , "file" , path , "error" , err )
@ -219,7 +219,7 @@ func (fr *FileReader) handleMissingDashboardFiles(ctx context.Context, provision
}
// saveDashboard saves or updates the dashboard provisioning file at path.
func ( fr * FileReader ) saveDashboard ( ctx context . Context , path string , folderID int64 , fileInfo os . FileInfo ,
func ( fr * FileReader ) saveDashboard ( ctx context . Context , path string , folderID int64 , folderUID string , f ileInfo os . FileInfo ,
provisionedDashboardRefs map [ string ] * dashboards . DashboardProvisioning ) ( provisioningMetadata , error ) {
provisioningMetadata := provisioningMetadata { }
resolvedFileInfo , err := resolveSymlink ( fileInfo , path )
@ -229,7 +229,7 @@ func (fr *FileReader) saveDashboard(ctx context.Context, path string, folderID i
provisionedData , alreadyProvisioned := provisionedDashboardRefs [ path ]
jsonFile , err := fr . readDashboardFromFile ( path , resolvedFileInfo . ModTime ( ) , folderID )
jsonFile , err := fr . readDashboardFromFile ( path , resolvedFileInfo . ModTime ( ) , folderID , folderUID )
if err != nil {
fr . log . Error ( "failed to load dashboard from " , "file" , path , "error" , err )
return provisioningMetadata , nil
@ -245,7 +245,25 @@ func (fr *FileReader) saveDashboard(ctx context.Context, path string, folderID i
provisioningMetadata . uid = dash . Dashboard . UID
provisioningMetadata . identity = dashboardIdentity { title : dash . Dashboard . Title , folderID : dash . Dashboard . FolderID }
// fix empty folder_uid from already provisioned dashboards
if upToDate && folderUID != "" {
d , err := fr . dashboardStore . GetDashboard (
ctx ,
& dashboards . GetDashboardQuery {
OrgID : jsonFile . dashboard . OrgID ,
UID : jsonFile . dashboard . Dashboard . UID ,
} ,
)
if err != nil {
return provisioningMetadata , err
}
if d . FolderUID != folderUID {
upToDate = false
}
}
if upToDate {
fr . log . Debug ( "provisioned dashboard is up to date" , "provisioner" , fr . Cfg . Name , "file" , path , "folderId" , dash . Dashboard . FolderID , "folderUid" , dash . Dashboard . FolderUID )
return provisioningMetadata , nil
}
@ -259,7 +277,7 @@ func (fr *FileReader) saveDashboard(ctx context.Context, path string, folderID i
}
if ! fr . isDatabaseAccessRestricted ( ) {
fr . log . Debug ( "saving new dashboard" , "provisioner" , fr . Cfg . Name , "file" , path , "folderId" , dash . Dashboard . FolderID )
fr . log . Debug ( "saving new dashboard" , "provisioner" , fr . Cfg . Name , "file" , path , "folderId" , dash . Dashboard . FolderID , "folderUid" , dash . Dashboard . FolderUID )
dp := & dashboards . DashboardProvisioning {
ExternalID : path ,
Name : fr . Cfg . Name ,
@ -293,9 +311,9 @@ func getProvisionedDashboardsByPath(ctx context.Context, service dashboards.Dash
return byPath , nil
}
func ( fr * FileReader ) getOrCreateFolderID ( ctx context . Context , cfg * config , service dashboards . DashboardProvisioningService , folderName string ) ( int64 , error ) {
func ( fr * FileReader ) getOrCreateFolder ( ctx context . Context , cfg * config , service dashboards . DashboardProvisioningService , folderName string ) ( int64 , string , error ) {
if folderName == "" {
return 0 , ErrFolderNameMissing
return 0 , "" , ErrFolderNameMissing
}
cmd := & dashboards . GetDashboardQuery {
@ -306,7 +324,7 @@ func (fr *FileReader) getOrCreateFolderID(ctx context.Context, cfg *config, serv
result , err := fr . dashboardStore . GetDashboard ( ctx , cmd )
if err != nil && ! errors . Is ( err , dashboards . ErrDashboardNotFound ) {
return 0 , err
return 0 , "" , err
}
// dashboard folder not found. create one.
@ -318,22 +336,22 @@ func (fr *FileReader) getOrCreateFolderID(ctx context.Context, cfg *config, serv
dash . OrgID = cfg . OrgID
// set dashboard folderUid if given
if cfg . FolderUID == accesscontrol . GeneralFolderUID {
return 0 , dashboards . ErrFolderInvalidUID
return 0 , "" , dashboards . ErrFolderInvalidUID
}
dash . Dashboard . SetUID ( cfg . FolderUID )
dbDash , err := service . SaveFolderForProvisionedDashboards ( ctx , dash )
if err != nil {
return 0 , err
return 0 , "" , err
}
return dbDash . ID , nil
return dbDash . ID , dbDash . UID , nil
}
if ! result . IsFolder {
return 0 , fmt . Errorf ( "got invalid response. expected folder, found dashboard" )
return 0 , "" , fmt . Errorf ( "got invalid response. expected folder, found dashboard" )
}
return result . ID , nil
return result . ID , result . UID , nil
}
func resolveSymlink ( fileinfo os . FileInfo , path string ) ( os . FileInfo , error ) {
@ -387,7 +405,7 @@ type dashboardJSONFile struct {
lastModified time . Time
}
func ( fr * FileReader ) readDashboardFromFile ( path string , lastModified time . Time , folderID int64 ) ( * dashboardJSONFile , error ) {
func ( fr * FileReader ) readDashboardFromFile ( path string , lastModified time . Time , folderID int64 , folderUID string ) ( * dashboardJSONFile , error ) {
// nolint:gosec
// We can ignore the gosec G304 warning on this one because `path` comes from the provisioning configuration file.
reader , err := os . Open ( path )
@ -415,7 +433,7 @@ func (fr *FileReader) readDashboardFromFile(path string, lastModified time.Time,
return nil , err
}
dash , err := createDashboardJSON ( data , lastModified , fr . Cfg , folderID )
dash , err := createDashboardJSON ( data , lastModified , fr . Cfg , folderID , folderUID )
if err != nil {
return nil , err
}