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
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, fileInfo, 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, fileInfo, 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, fileInfo 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
}

@ -364,7 +364,7 @@ func TestDashboardFileReader(t *testing.T) {
r, err := NewDashboardFileReader(cfg, logger, nil, nil)
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)
})
@ -384,7 +384,7 @@ func TestDashboardFileReader(t *testing.T) {
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
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)
})
@ -404,7 +404,7 @@ func TestDashboardFileReader(t *testing.T) {
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
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)
})

@ -56,7 +56,7 @@ type configs struct {
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.Dashboard = dashboards.NewDashboardFromJson(data)
dash.UpdatedAt = lastModified
@ -64,6 +64,7 @@ func createDashboardJSON(data *simplejson.Json, lastModified time.Time, cfg *con
dash.OrgID = cfg.OrgID
dash.Dashboard.OrgID = cfg.OrgID
dash.Dashboard.FolderID = folderID
dash.Dashboard.FolderUID = folderUID
if dash.Dashboard.Title == "" {
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("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)
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
folderID, _, err := r.getOrCreateFolder(context.Background(), cfg, fakeService, folderName)
require.NoError(t, err)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
@ -92,7 +92,7 @@ func TestDuplicatesValidator(t *testing.T) {
fakeStore := &fakeDashboardStore{}
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
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)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
@ -194,7 +194,7 @@ func TestDuplicatesValidator(t *testing.T) {
r, err := NewDashboardFileReader(cfg, logger, nil, fakeStore)
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)
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
@ -211,7 +211,7 @@ func TestDuplicatesValidator(t *testing.T) {
r, err = NewDashboardFileReader(cfg3, logger, nil, fakeStore)
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)
identity = dashboardIdentity{folderID: folderID, title: "Grafana"}

Loading…
Cancel
Save