Provisioning: Set `dashboard.folder_uid` column for provisioned dashboards (#77637)

Provisiong: Store folder UID to provisioned dashboards
pull/77042/head^2
Sofia Papagiannaki 2 years ago committed by GitHub
parent f6c5b80cca
commit 2598ff7c93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 52
      pkg/services/provisioning/dashboards/file_reader.go
  2. 6
      pkg/services/provisioning/dashboards/file_reader_test.go
  3. 3
      pkg/services/provisioning/dashboards/types.go
  4. 8
      pkg/services/provisioning/dashboards/validator_test.go

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

@ -364,7 +364,7 @@ func TestDashboardFileReader(t *testing.T) {
r, err := NewDashboardFileReader(cfg, logger, nil, nil) r, err := NewDashboardFileReader(cfg, logger, nil, nil)
require.NoError(t, err) require.NoError(t, err)
_, err = r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder) _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder)
require.Equal(t, err, ErrFolderNameMissing) require.Equal(t, err, ErrFolderNameMissing)
}) })
@ -384,7 +384,7 @@ func TestDashboardFileReader(t *testing.T) {
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
require.NoError(t, err) require.NoError(t, err)
_, err = r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder) _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder)
require.NoError(t, err) require.NoError(t, err)
}) })
@ -404,7 +404,7 @@ func TestDashboardFileReader(t *testing.T) {
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
require.NoError(t, err) require.NoError(t, err)
_, err = r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder) _, _, err = r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg.Folder)
require.ErrorIs(t, err, dashboards.ErrFolderInvalidUID) require.ErrorIs(t, err, dashboards.ErrFolderInvalidUID)
}) })

@ -56,7 +56,7 @@ type configs struct {
AllowUIUpdates values.BoolValue `json:"allowUiUpdates" yaml:"allowUiUpdates"` AllowUIUpdates values.BoolValue `json:"allowUiUpdates" yaml:"allowUiUpdates"`
} }
func createDashboardJSON(data *simplejson.Json, lastModified time.Time, cfg *config, folderID int64) (*dashboards.SaveDashboardDTO, error) { func createDashboardJSON(data *simplejson.Json, lastModified time.Time, cfg *config, folderID int64, folderUID string) (*dashboards.SaveDashboardDTO, error) {
dash := &dashboards.SaveDashboardDTO{} dash := &dashboards.SaveDashboardDTO{}
dash.Dashboard = dashboards.NewDashboardFromJson(data) dash.Dashboard = dashboards.NewDashboardFromJson(data)
dash.UpdatedAt = lastModified dash.UpdatedAt = lastModified
@ -64,6 +64,7 @@ func createDashboardJSON(data *simplejson.Json, lastModified time.Time, cfg *con
dash.OrgID = cfg.OrgID dash.OrgID = cfg.OrgID
dash.Dashboard.OrgID = cfg.OrgID dash.Dashboard.OrgID = cfg.OrgID
dash.Dashboard.FolderID = folderID dash.Dashboard.FolderID = folderID
dash.Dashboard.FolderUID = folderUID
if dash.Dashboard.Title == "" { if dash.Dashboard.Title == "" {
return nil, dashboards.ErrDashboardTitleEmpty return nil, dashboards.ErrDashboardTitleEmpty

@ -39,7 +39,7 @@ func TestDuplicatesValidator(t *testing.T) {
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(6) fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(6)
fakeService.On("GetProvisionedDashboardData", mock.Anything, mock.AnythingOfType("string")).Return([]*dashboards.DashboardProvisioning{}, nil).Times(4) fakeService.On("GetProvisionedDashboardData", mock.Anything, mock.AnythingOfType("string")).Return([]*dashboards.DashboardProvisioning{}, nil).Times(4)
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(5) fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&dashboards.Dashboard{}, nil).Times(5)
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, folderName) folderID, _, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, folderName)
require.NoError(t, err) require.NoError(t, err)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"} identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
@ -92,7 +92,7 @@ func TestDuplicatesValidator(t *testing.T) {
fakeStore := &fakeDashboardStore{} fakeStore := &fakeDashboardStore{}
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
require.NoError(t, err) require.NoError(t, err)
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, folderName) folderID, _, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, folderName)
require.NoError(t, err) require.NoError(t, err)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"} identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
@ -194,7 +194,7 @@ func TestDuplicatesValidator(t *testing.T) {
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore) r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
require.NoError(t, err) require.NoError(t, err)
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg1.Folder) folderID, _, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, cfg1.Folder)
require.NoError(t, err) require.NoError(t, err)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"} identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
@ -211,7 +211,7 @@ func TestDuplicatesValidator(t *testing.T) {
r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore) r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore)
require.NoError(t, err) require.NoError(t, err)
folderID, err = r.getOrCreateFolderID(context.Background(), cfg3, fakeService, cfg3.Folder) folderID, _, err = r.getOrCreateFolder(context.Background(), cfg3, fakeService, cfg3.Folder)
require.NoError(t, err) require.NoError(t, err)
identity = dashboardIdentity{folderID: folderID, title: "Grafana"} identity = dashboardIdentity{folderID: folderID, title: "Grafana"}

Loading…
Cancel
Save