mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Chore: Remove bus.Dispatch from provisioning services (#44989)
* make getordbyname a method * remove one dispatch from plugins provisioner * remove bus from the plugins provisioner, skip test for now * remove bus from datasource provisioning * resolve tests in notifier provisioning * remove bus from the dashboards provisioning service * fix missing struct field * fix getorgbyid method calls * pass org store into dashboard provisioner * fix test function prototype * fix tests * attempt to fix tests after the rebase * fix integration test * avoid using transaction * remove comments
This commit is contained in:
parent
49ac8b9f0a
commit
a231c6861c
@ -204,7 +204,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
|
||||
// org information available to all users.
|
||||
apiRoute.Group("/org", func(orgRoute routing.RouteRegister) {
|
||||
orgRoute.Get("/", authorize(reqSignedIn, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(GetCurrentOrg))
|
||||
orgRoute.Get("/", authorize(reqSignedIn, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(hs.GetCurrentOrg))
|
||||
orgRoute.Get("/quotas", authorize(reqSignedIn, ac.EvalPermission(ActionOrgsQuotasRead)), routing.Wrap(hs.GetCurrentOrgQuotas))
|
||||
})
|
||||
|
||||
@ -243,7 +243,7 @@ func (hs *HTTPServer) registerRoutes() {
|
||||
// orgs (admin routes)
|
||||
apiRoute.Group("/orgs/:orgId", func(orgsRoute routing.RouteRegister) {
|
||||
userIDScope := ac.Scope("users", "id", ac.Parameter(":userId"))
|
||||
orgsRoute.Get("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(GetOrgByID))
|
||||
orgsRoute.Get("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsRead)), routing.Wrap(hs.GetOrgByID))
|
||||
orgsRoute.Put("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(hs.UpdateOrg))
|
||||
orgsRoute.Put("/address", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsWrite)), routing.Wrap(hs.UpdateOrgAddress))
|
||||
orgsRoute.Delete("/", authorizeInOrg(reqGrafanaAdmin, acmiddleware.UseOrgFromContextParams, ac.EvalPermission(ActionOrgsDelete)), routing.Wrap(hs.DeleteOrgByID))
|
||||
|
@ -18,17 +18,17 @@ import (
|
||||
)
|
||||
|
||||
// GET /api/org
|
||||
func GetCurrentOrg(c *models.ReqContext) response.Response {
|
||||
return getOrgHelper(c.Req.Context(), c.OrgId)
|
||||
func (hs *HTTPServer) GetCurrentOrg(c *models.ReqContext) response.Response {
|
||||
return hs.getOrgHelper(c.Req.Context(), c.OrgId)
|
||||
}
|
||||
|
||||
// GET /api/orgs/:orgId
|
||||
func GetOrgByID(c *models.ReqContext) response.Response {
|
||||
func (hs *HTTPServer) GetOrgByID(c *models.ReqContext) response.Response {
|
||||
orgId, err := strconv.ParseInt(web.Params(c.Req)[":orgId"], 10, 64)
|
||||
if err != nil {
|
||||
return response.Error(http.StatusBadRequest, "orgId is invalid", err)
|
||||
}
|
||||
return getOrgHelper(c.Req.Context(), orgId)
|
||||
return hs.getOrgHelper(c.Req.Context(), orgId)
|
||||
}
|
||||
|
||||
// GET /api/orgs/name/:name
|
||||
@ -57,10 +57,10 @@ func (hs *HTTPServer) GetOrgByName(c *models.ReqContext) response.Response {
|
||||
return response.JSON(200, &result)
|
||||
}
|
||||
|
||||
func getOrgHelper(ctx context.Context, orgID int64) response.Response {
|
||||
func (hs *HTTPServer) getOrgHelper(ctx context.Context, orgID int64) response.Response {
|
||||
query := models.GetOrgByIdQuery{Id: orgID}
|
||||
|
||||
if err := sqlstore.GetOrgById(ctx, &query); err != nil {
|
||||
if err := hs.SQLStore.GetOrgById(ctx, &query); err != nil {
|
||||
if errors.Is(err, models.ErrOrgNotFound) {
|
||||
return response.Error(404, "Organization not found", err)
|
||||
}
|
||||
|
@ -26,6 +26,7 @@ type DashboardProvisioningService interface {
|
||||
GetProvisionedDashboardDataByDashboardID(dashboardID int64) (*models.DashboardProvisioning, error)
|
||||
UnprovisionDashboard(ctx context.Context, dashboardID int64) error
|
||||
DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error
|
||||
DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error
|
||||
}
|
||||
|
||||
//go:generate mockery --name Store --structname FakeDashboardStore --output database --outpkg database --filename database_mock.go
|
||||
@ -41,6 +42,7 @@ type Store interface {
|
||||
SaveProvisionedDashboard(cmd models.SaveDashboardCommand, provisioning *models.DashboardProvisioning) (*models.Dashboard, error)
|
||||
SaveDashboard(cmd models.SaveDashboardCommand) (*models.Dashboard, error)
|
||||
UpdateDashboardACL(ctx context.Context, uid int64, items []*models.DashboardAcl) error
|
||||
DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error
|
||||
// SaveAlerts saves dashboard alerts.
|
||||
SaveAlerts(ctx context.Context, dashID int64, alerts []*models.Alert) error
|
||||
UnprovisionDashboard(ctx context.Context, id int64) error
|
||||
|
@ -14,6 +14,20 @@ type FakeDashboardProvisioning struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// DeleteOrphanedProvisionedDashboards provides a mock function with given fields: ctx, cmd
|
||||
func (_m *FakeDashboardProvisioning) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.DeleteOrphanedProvisionedDashboardsCommand) error); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// DeleteProvisionedDashboard provides a mock function with given fields: ctx, dashboardID, orgID
|
||||
func (_m *FakeDashboardProvisioning) DeleteProvisionedDashboard(ctx context.Context, dashboardID int64, orgID int64) error {
|
||||
ret := _m.Called(ctx, dashboardID, orgID)
|
||||
|
@ -42,3 +42,6 @@ func (s *FakeDashboardService) DeleteDashboard(ctx context.Context, dashboardId
|
||||
func (s *FakeDashboardService) GetProvisionedDashboardDataByDashboardID(id int64) (*models.DashboardProvisioning, error) {
|
||||
return s.ProvisionedDashData, nil
|
||||
}
|
||||
func (s *FakeDashboardService) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
return nil
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
@ -195,6 +196,31 @@ func (d *DashboardStore) UnprovisionDashboard(ctx context.Context, id int64) err
|
||||
})
|
||||
}
|
||||
|
||||
func (d *DashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
return d.sqlStore.WithDbSession(ctx, func(sess *sqlstore.DBSession) error {
|
||||
var result []*models.DashboardProvisioning
|
||||
|
||||
convertedReaderNames := make([]interface{}, len(cmd.ReaderNames))
|
||||
for index, readerName := range cmd.ReaderNames {
|
||||
convertedReaderNames[index] = readerName
|
||||
}
|
||||
|
||||
err := sess.NotIn("name", convertedReaderNames...).Find(&result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, deleteDashCommand := range result {
|
||||
err := d.sqlStore.DeleteDashboard(ctx, &models.DeleteDashboardCommand{Id: deleteDashCommand.DashboardId})
|
||||
if err != nil && !errors.Is(err, models.ErrDashboardNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
}
|
||||
|
||||
func getExistingDashboardByIdOrUidForUpdate(sess *sqlstore.DBSession, dash *models.Dashboard, dialect migrator.Dialect, overwrite bool) (bool, error) {
|
||||
dashWithIdExists := false
|
||||
isParentFolderChanged := false
|
||||
|
@ -15,6 +15,20 @@ type FakeDashboardStore struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// DeleteOrphanedProvisionedDashboards provides a mock function with given fields: ctx, cmd
|
||||
func (_m *FakeDashboardStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.DeleteOrphanedProvisionedDashboardsCommand) error); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetFolderByTitle provides a mock function with given fields: orgID, title
|
||||
func (_m *FakeDashboardStore) GetFolderByTitle(orgID int64, title string) (*models.Dashboard, error) {
|
||||
ret := _m.Called(orgID, title)
|
||||
|
@ -5,10 +5,11 @@ package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
|
||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
|
||||
@ -82,7 +83,7 @@ func TestDashboardProvisioningTest(t *testing.T) {
|
||||
require.NotNil(t, query.Result)
|
||||
|
||||
deleteCmd := &models.DeleteOrphanedProvisionedDashboardsCommand{ReaderNames: []string{"default"}}
|
||||
require.Nil(t, sqlStore.DeleteOrphanedProvisionedDashboards(context.Background(), deleteCmd))
|
||||
require.Nil(t, dashboardStore.DeleteOrphanedProvisionedDashboards(context.Background(), deleteCmd))
|
||||
|
||||
query = &models.GetDashboardsQuery{DashboardIds: []int64{dash.Id, anotherDash.Id}}
|
||||
err = sqlStore.GetDashboards(context.Background(), query)
|
||||
|
@ -135,6 +135,10 @@ func (dr *DashboardServiceImpl) UpdateDashboardACL(ctx context.Context, uid int6
|
||||
return dr.dashboardStore.UpdateDashboardACL(ctx, uid, items)
|
||||
}
|
||||
|
||||
func (dr *DashboardServiceImpl) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
return dr.dashboardStore.DeleteOrphanedProvisionedDashboards(ctx, cmd)
|
||||
}
|
||||
|
||||
var validateAlerts = func(ctx context.Context, dash *models.Dashboard, user *models.SignedInUser) error {
|
||||
extractor := alerting.NewDashAlertExtractor(dash, dash.OrgId, user)
|
||||
return extractor.ValidateAlerts(ctx)
|
||||
|
@ -14,8 +14,9 @@ import (
|
||||
)
|
||||
|
||||
type configReader struct {
|
||||
path string
|
||||
log log.Logger
|
||||
path string
|
||||
log log.Logger
|
||||
orgStore utils.OrgStore
|
||||
}
|
||||
|
||||
func (cr *configReader) parseConfigs(file os.FileInfo) ([]*config, error) {
|
||||
@ -93,7 +94,7 @@ func (cr *configReader) readConfig(ctx context.Context) ([]*config, error) {
|
||||
dashboard.OrgID = 1
|
||||
}
|
||||
|
||||
if err := utils.CheckOrgExists(ctx, dashboard.OrgID); err != nil {
|
||||
if err := utils.CheckOrgExists(ctx, cr.orgStore, dashboard.OrgID); err != nil {
|
||||
return nil, fmt.Errorf("failed to provision dashboards with %q reader: %w", dashboard.Name, err)
|
||||
}
|
||||
|
||||
|
@ -25,10 +25,10 @@ var (
|
||||
func TestDashboardsAsConfig(t *testing.T) {
|
||||
t.Run("Dashboards as configuration", func(t *testing.T) {
|
||||
logger := log.New("test-logger")
|
||||
sqlstore.InitTestDB(t)
|
||||
store := sqlstore.InitTestDB(t)
|
||||
|
||||
t.Run("Should fail if orgs don't exist in the database", func(t *testing.T) {
|
||||
cfgProvider := configReader{path: appliedDefaults, log: logger}
|
||||
cfgProvider := configReader{path: appliedDefaults, log: logger, orgStore: store}
|
||||
_, err := cfgProvider.readConfig(context.Background())
|
||||
require.Error(t, err)
|
||||
assert.True(t, errors.Is(err, models.ErrOrgNotFound))
|
||||
@ -41,7 +41,7 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
}
|
||||
|
||||
t.Run("default values should be applied", func(t *testing.T) {
|
||||
cfgProvider := configReader{path: appliedDefaults, log: logger}
|
||||
cfgProvider := configReader{path: appliedDefaults, log: logger, orgStore: store}
|
||||
cfg, err := cfgProvider.readConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -52,7 +52,7 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
|
||||
t.Run("Can read config file version 1 format", func(t *testing.T) {
|
||||
_ = os.Setenv("TEST_VAR", "general")
|
||||
cfgProvider := configReader{path: simpleDashboardConfig, log: logger}
|
||||
cfgProvider := configReader{path: simpleDashboardConfig, log: logger, orgStore: store}
|
||||
cfg, err := cfgProvider.readConfig(context.Background())
|
||||
_ = os.Unsetenv("TEST_VAR")
|
||||
require.NoError(t, err)
|
||||
@ -61,7 +61,7 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Can read config file in version 0 format", func(t *testing.T) {
|
||||
cfgProvider := configReader{path: oldVersion, log: logger}
|
||||
cfgProvider := configReader{path: oldVersion, log: logger, orgStore: store}
|
||||
cfg, err := cfgProvider.readConfig(context.Background())
|
||||
require.NoError(t, err)
|
||||
|
||||
@ -69,7 +69,7 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should skip invalid path", func(t *testing.T) {
|
||||
cfgProvider := configReader{path: "/invalid-directory", log: logger}
|
||||
cfgProvider := configReader{path: "/invalid-directory", log: logger, orgStore: store}
|
||||
cfg, err := cfgProvider.readConfig(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
@ -79,7 +79,7 @@ func TestDashboardsAsConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should skip broken config files", func(t *testing.T) {
|
||||
cfgProvider := configReader{path: brokenConfigs, log: logger}
|
||||
cfgProvider := configReader{path: brokenConfigs, log: logger, orgStore: store}
|
||||
cfg, err := cfgProvider.readConfig(context.Background())
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
|
@ -5,10 +5,10 @@ import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/utils"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
)
|
||||
|
||||
@ -23,7 +23,7 @@ type DashboardProvisioner interface {
|
||||
}
|
||||
|
||||
// DashboardProvisionerFactory creates DashboardProvisioners based on input
|
||||
type DashboardProvisionerFactory func(context.Context, string, dashboards.DashboardProvisioningService) (DashboardProvisioner, error)
|
||||
type DashboardProvisionerFactory func(context.Context, string, dashboards.DashboardProvisioningService, utils.OrgStore) (DashboardProvisioner, error)
|
||||
|
||||
// Provisioner is responsible for syncing dashboard from disk to Grafana's database.
|
||||
type Provisioner struct {
|
||||
@ -31,18 +31,19 @@ type Provisioner struct {
|
||||
fileReaders []*FileReader
|
||||
configs []*config
|
||||
duplicateValidator duplicateValidator
|
||||
provisioner dashboards.DashboardProvisioningService
|
||||
}
|
||||
|
||||
// New returns a new DashboardProvisioner
|
||||
func New(ctx context.Context, configDirectory string, service dashboards.DashboardProvisioningService) (DashboardProvisioner, error) {
|
||||
func New(ctx context.Context, configDirectory string, provisioner dashboards.DashboardProvisioningService, orgStore utils.OrgStore) (DashboardProvisioner, error) {
|
||||
logger := log.New("provisioning.dashboard")
|
||||
cfgReader := &configReader{path: configDirectory, log: logger}
|
||||
cfgReader := &configReader{path: configDirectory, log: logger, orgStore: orgStore}
|
||||
configs, err := cfgReader.readConfig(ctx)
|
||||
if err != nil {
|
||||
return nil, errutil.Wrap("Failed to read dashboards config", err)
|
||||
}
|
||||
|
||||
fileReaders, err := getFileReaders(configs, logger, service)
|
||||
fileReaders, err := getFileReaders(configs, logger, provisioner)
|
||||
if err != nil {
|
||||
return nil, errutil.Wrap("Failed to initialize file readers", err)
|
||||
}
|
||||
@ -52,6 +53,7 @@ func New(ctx context.Context, configDirectory string, service dashboards.Dashboa
|
||||
fileReaders: fileReaders,
|
||||
configs: configs,
|
||||
duplicateValidator: newDuplicateValidator(logger, fileReaders),
|
||||
provisioner: provisioner,
|
||||
}
|
||||
|
||||
return d, nil
|
||||
@ -84,7 +86,7 @@ func (provider *Provisioner) CleanUpOrphanedDashboards(ctx context.Context) {
|
||||
currentReaders[index] = reader.Cfg.Name
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(ctx, &models.DeleteOrphanedProvisionedDashboardsCommand{ReaderNames: currentReaders}); err != nil {
|
||||
if err := provider.provisioner.DeleteOrphanedProvisionedDashboards(ctx, &models.DeleteOrphanedProvisionedDashboardsCommand{ReaderNames: currentReaders}); err != nil {
|
||||
provider.log.Warn("Failed to delete orphaned provisioned dashboards", "err", err)
|
||||
}
|
||||
}
|
||||
|
@ -138,7 +138,7 @@ 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]*models.DashboardProvisioning, usageTracker *usageTracker) error {
|
||||
folderID, err := getOrCreateFolderID(ctx, fr.Cfg, fr.dashboardProvisioningService, fr.Cfg.Folder)
|
||||
folderID, err := fr.getOrCreateFolderID(ctx, fr.Cfg, fr.dashboardProvisioningService, fr.Cfg.Folder)
|
||||
if err != nil && !errors.Is(err, ErrFolderNameMissing) {
|
||||
return err
|
||||
}
|
||||
@ -168,7 +168,7 @@ func (fr *FileReader) storeDashboardsInFoldersFromFileStructure(ctx context.Cont
|
||||
folderName = filepath.Base(dashboardsFolder)
|
||||
}
|
||||
|
||||
folderID, err := getOrCreateFolderID(ctx, fr.Cfg, fr.dashboardProvisioningService, folderName)
|
||||
folderID, err := fr.getOrCreateFolderID(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)
|
||||
}
|
||||
@ -290,7 +290,7 @@ func getProvisionedDashboardsByPath(service dashboards.DashboardProvisioningServ
|
||||
return byPath, nil
|
||||
}
|
||||
|
||||
func getOrCreateFolderID(ctx context.Context, cfg *config, service dashboards.DashboardProvisioningService, folderName string) (int64, error) {
|
||||
func (fr *FileReader) getOrCreateFolderID(ctx context.Context, cfg *config, service dashboards.DashboardProvisioningService, folderName string) (int64, error) {
|
||||
if folderName == "" {
|
||||
return 0, ErrFolderNameMissing
|
||||
}
|
||||
|
@ -364,8 +364,10 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
"folder": defaultDashboards,
|
||||
},
|
||||
}
|
||||
r, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
|
||||
_, err = r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
|
||||
require.Equal(t, err, ErrFolderNameMissing)
|
||||
})
|
||||
|
||||
@ -380,9 +382,12 @@ func TestDashboardFileReader(t *testing.T) {
|
||||
"folder": defaultDashboards,
|
||||
},
|
||||
}
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{Id: 1}, nil).Once()
|
||||
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Once()
|
||||
_, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
|
||||
r, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
require.NoError(t, err)
|
||||
|
||||
_, err = r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg.Folder)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
|
@ -29,18 +29,18 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
Type: "file",
|
||||
OrgID: 1,
|
||||
Folder: "",
|
||||
Options: map[string]interface{}{},
|
||||
Options: map[string]interface{}{"path": dashboardContainingUID},
|
||||
}
|
||||
logger := log.New("test.logger")
|
||||
|
||||
t.Run("Duplicates validator should collect info about duplicate UIDs and titles within folders", func(t *testing.T) {
|
||||
const folderName = "duplicates-validator-folder"
|
||||
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(3)
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(2)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
|
||||
|
||||
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
|
||||
r, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
require.NoError(t, err)
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(6)
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(4)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(5)
|
||||
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
|
||||
require.NoError(t, err)
|
||||
|
||||
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
|
||||
@ -89,12 +89,9 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
|
||||
t.Run("Duplicates validator should not collect info about duplicate UIDs and titles within folders for different orgs", func(t *testing.T) {
|
||||
const folderName = "duplicates-validator-folder"
|
||||
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(3)
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(2)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(2)
|
||||
|
||||
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
|
||||
r, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
require.NoError(t, err)
|
||||
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, folderName)
|
||||
require.NoError(t, err)
|
||||
|
||||
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
|
||||
@ -154,7 +151,7 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
t.Run("Duplicates validator should restrict write access only for readers with duplicates", func(t *testing.T) {
|
||||
fakeService.On("SaveFolderForProvisionedDashboards", mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(5)
|
||||
fakeService.On("GetProvisionedDashboardData", mock.Anything).Return([]*models.DashboardProvisioning{}, nil).Times(3)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(6)
|
||||
fakeService.On("SaveProvisionedDashboard", mock.Anything, mock.Anything, mock.Anything).Return(&models.Dashboard{}, nil).Times(5)
|
||||
|
||||
cfg1 := &config{
|
||||
Name: "first", Type: "file", OrgID: 1, Folder: "duplicates-validator-folder",
|
||||
@ -194,7 +191,9 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
|
||||
duplicates := duplicateValidator.getDuplicates()
|
||||
|
||||
folderID, err := getOrCreateFolderID(context.Background(), cfg, fakeService, cfg1.Folder)
|
||||
r, err := NewDashboardFileReader(cfg, logger, nil)
|
||||
require.NoError(t, err)
|
||||
folderID, err := r.getOrCreateFolderID(context.Background(), cfg, fakeService, cfg1.Folder)
|
||||
require.NoError(t, err)
|
||||
|
||||
identity := dashboardIdentity{folderID: folderID, title: "Grafana"}
|
||||
@ -209,7 +208,9 @@ func TestDuplicatesValidator(t *testing.T) {
|
||||
sort.Strings(titleUsageReaders)
|
||||
require.Equal(t, []string{"first"}, titleUsageReaders)
|
||||
|
||||
folderID, err = getOrCreateFolderID(context.Background(), cfg3, fakeService, cfg3.Folder)
|
||||
r, err = NewDashboardFileReader(cfg3, logger, nil)
|
||||
require.NoError(t, err)
|
||||
folderID, err = r.getOrCreateFolderID(context.Background(), cfg3, fakeService, cfg3.Folder)
|
||||
require.NoError(t, err)
|
||||
|
||||
identity = dashboardIdentity{folderID: folderID, title: "Grafana"}
|
||||
|
@ -16,7 +16,8 @@ import (
|
||||
)
|
||||
|
||||
type configReader struct {
|
||||
log log.Logger
|
||||
log log.Logger
|
||||
orgStore utils.OrgStore
|
||||
}
|
||||
|
||||
func (cr *configReader) readConfig(ctx context.Context, path string) ([]*configs, error) {
|
||||
@ -129,7 +130,7 @@ func (cr *configReader) validateDefaultUniqueness(ctx context.Context, datasourc
|
||||
}
|
||||
|
||||
func (cr *configReader) validateAccessAndOrgID(ctx context.Context, ds *upsertDataSourceFromConfig) error {
|
||||
if err := utils.CheckOrgExists(ctx, ds.OrgID); err != nil {
|
||||
if err := utils.CheckOrgExists(ctx, cr.orgStore, ds.OrgID); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"os"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -26,173 +25,133 @@ var (
|
||||
multipleOrgsWithDefault = "testdata/multiple-org-default"
|
||||
withoutDefaults = "testdata/appliedDefaults"
|
||||
invalidAccess = "testdata/invalid-access"
|
||||
|
||||
fakeRepo *fakeRepository
|
||||
)
|
||||
|
||||
func TestDatasourceAsConfig(t *testing.T) {
|
||||
setup := func() {
|
||||
fakeRepo = &fakeRepository{}
|
||||
bus.ClearBusHandlers()
|
||||
bus.AddHandler("test", mockDelete)
|
||||
bus.AddHandler("test", mockInsert)
|
||||
bus.AddHandler("test", mockUpdate)
|
||||
bus.AddHandler("test", mockGet)
|
||||
bus.AddHandler("test", mockGetOrg)
|
||||
}
|
||||
t.Run("when some values missing should apply default on insert", func(t *testing.T) {
|
||||
store := &spyStore{}
|
||||
orgStore := &mockOrgStore{ExpectedOrg: &models.Org{Id: 1}}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), withoutDefaults)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
t.Run("when some values missing", func(t *testing.T) {
|
||||
t.Run("should apply default on insert", func(t *testing.T) {
|
||||
setup()
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(context.Background(), withoutDefaults)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
require.Equal(t, len(store.inserted), 1)
|
||||
require.Equal(t, store.inserted[0].OrgId, int64(1))
|
||||
require.Equal(t, store.inserted[0].Access, models.DsAccess("proxy"))
|
||||
require.Equal(t, store.inserted[0].Name, "My datasource name")
|
||||
require.Equal(t, store.inserted[0].Uid, "P2AD1F727255C56BA")
|
||||
})
|
||||
|
||||
require.Equal(t, len(fakeRepo.inserted), 1)
|
||||
require.Equal(t, fakeRepo.inserted[0].OrgId, int64(1))
|
||||
require.Equal(t, fakeRepo.inserted[0].Access, models.DsAccess("proxy"))
|
||||
require.Equal(t, fakeRepo.inserted[0].Name, "My datasource name")
|
||||
require.Equal(t, fakeRepo.inserted[0].Uid, "P2AD1F727255C56BA")
|
||||
})
|
||||
t.Run("when some values missing should not change UID when updates", func(t *testing.T) {
|
||||
store := &spyStore{
|
||||
items: []*models.DataSource{{Name: "My datasource name", OrgId: 1, Id: 1, Uid: util.GenerateShortUID()}},
|
||||
}
|
||||
orgStore := &mockOrgStore{}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), withoutDefaults)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
t.Run("should not change UID when updates", func(t *testing.T) {
|
||||
setup()
|
||||
|
||||
fakeRepo.loadAll = []*models.DataSource{
|
||||
{Name: "My datasource name", OrgId: 1, Id: 1, Uid: util.GenerateShortUID()},
|
||||
}
|
||||
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(context.Background(), withoutDefaults)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
require.Equal(t, len(fakeRepo.deleted), 0)
|
||||
require.Equal(t, len(fakeRepo.inserted), 0)
|
||||
require.Equal(t, len(fakeRepo.updated), 1)
|
||||
require.Equal(t, "", fakeRepo.updated[0].Uid) // XORM will not update the field if its value is default
|
||||
})
|
||||
require.Equal(t, len(store.deleted), 0)
|
||||
require.Equal(t, len(store.inserted), 0)
|
||||
require.Equal(t, len(store.updated), 1)
|
||||
require.Equal(t, "", store.updated[0].Uid) // XORM will not update the field if its value is default
|
||||
})
|
||||
|
||||
t.Run("no datasource in database", func(t *testing.T) {
|
||||
setup()
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
store := &spyStore{}
|
||||
orgStore := &mockOrgStore{}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
require.Equal(t, len(fakeRepo.deleted), 0)
|
||||
require.Equal(t, len(fakeRepo.inserted), 2)
|
||||
require.Equal(t, len(fakeRepo.updated), 0)
|
||||
require.Equal(t, len(store.deleted), 0)
|
||||
require.Equal(t, len(store.inserted), 2)
|
||||
require.Equal(t, len(store.updated), 0)
|
||||
})
|
||||
|
||||
t.Run("One datasource in database with same name", func(t *testing.T) {
|
||||
setup()
|
||||
fakeRepo.loadAll = []*models.DataSource{
|
||||
{Name: "Graphite", OrgId: 1, Id: 1},
|
||||
t.Run("One datasource in database with same name should update one datasource", func(t *testing.T) {
|
||||
store := &spyStore{items: []*models.DataSource{{Name: "Graphite", OrgId: 1, Id: 1}}}
|
||||
orgStore := &mockOrgStore{}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
t.Run("should update one datasource", func(t *testing.T) {
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
require.Equal(t, len(fakeRepo.deleted), 0)
|
||||
require.Equal(t, len(fakeRepo.inserted), 1)
|
||||
require.Equal(t, len(fakeRepo.updated), 1)
|
||||
})
|
||||
require.Equal(t, len(store.deleted), 0)
|
||||
require.Equal(t, len(store.inserted), 1)
|
||||
require.Equal(t, len(store.updated), 1)
|
||||
})
|
||||
|
||||
t.Run("Two datasources with is_default", func(t *testing.T) {
|
||||
setup()
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
t.Run("Two datasources with is_default should raise error", func(t *testing.T) {
|
||||
store := &spyStore{}
|
||||
orgStore := &mockOrgStore{}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), doubleDatasourcesConfig)
|
||||
t.Run("should raise error", func(t *testing.T) { require.Equal(t, err, ErrInvalidConfigToManyDefault) })
|
||||
require.Equal(t, err, ErrInvalidConfigToManyDefault)
|
||||
})
|
||||
|
||||
t.Run("Multiple datasources in different organizations with isDefault in each organization", func(t *testing.T) {
|
||||
setup()
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
t.Run("Multiple datasources in different organizations with isDefault in each organization should not raise error", func(t *testing.T) {
|
||||
store := &spyStore{}
|
||||
orgStore := &mockOrgStore{}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), multipleOrgsWithDefault)
|
||||
t.Run("should not raise error", func(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(fakeRepo.inserted), 4)
|
||||
require.True(t, fakeRepo.inserted[0].IsDefault)
|
||||
require.Equal(t, fakeRepo.inserted[0].OrgId, int64(1))
|
||||
require.True(t, fakeRepo.inserted[2].IsDefault)
|
||||
require.Equal(t, fakeRepo.inserted[2].OrgId, int64(2))
|
||||
})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, len(store.inserted), 4)
|
||||
require.True(t, store.inserted[0].IsDefault)
|
||||
require.Equal(t, store.inserted[0].OrgId, int64(1))
|
||||
require.True(t, store.inserted[2].IsDefault)
|
||||
require.Equal(t, store.inserted[2].OrgId, int64(2))
|
||||
})
|
||||
|
||||
t.Run("Remove one datasource", func(t *testing.T) {
|
||||
setup()
|
||||
t.Run("Remove one datasource", func(t *testing.T) {
|
||||
fakeRepo.loadAll = []*models.DataSource{}
|
||||
t.Run("Remove one datasource should have removed old datasource", func(t *testing.T) {
|
||||
store := &spyStore{}
|
||||
orgStore := &mockOrgStore{}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), deleteOneDatasource)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
t.Run("should have removed old datasource", func(t *testing.T) {
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(context.Background(), deleteOneDatasource)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
require.Equal(t, 1, len(fakeRepo.deleted))
|
||||
// should have set OrgID to 1
|
||||
require.Equal(t, fakeRepo.deleted[0].OrgID, int64(1))
|
||||
require.Equal(t, 0, len(fakeRepo.inserted))
|
||||
require.Equal(t, len(fakeRepo.updated), 0)
|
||||
})
|
||||
})
|
||||
require.Equal(t, 1, len(store.deleted))
|
||||
// should have set OrgID to 1
|
||||
require.Equal(t, store.deleted[0].OrgID, int64(1))
|
||||
require.Equal(t, 0, len(store.inserted))
|
||||
require.Equal(t, len(store.updated), 0)
|
||||
})
|
||||
|
||||
t.Run("Two configured datasource and purge others ", func(t *testing.T) {
|
||||
setup()
|
||||
t.Run("two other datasources in database", func(t *testing.T) {
|
||||
fakeRepo.loadAll = []*models.DataSource{
|
||||
{Name: "old-graphite", OrgId: 1, Id: 1},
|
||||
{Name: "old-graphite2", OrgId: 1, Id: 2},
|
||||
}
|
||||
t.Run("Two configured datasource and purge others", func(t *testing.T) {
|
||||
store := &spyStore{items: []*models.DataSource{{Name: "old-graphite", OrgId: 1, Id: 1}, {Name: "old-graphite2", OrgId: 1, Id: 2}}}
|
||||
orgStore := &mockOrgStore{}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), twoDatasourcesConfigPurgeOthers)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
t.Run("should have two new datasources", func(t *testing.T) {
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(context.Background(), twoDatasourcesConfigPurgeOthers)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
require.Equal(t, len(fakeRepo.deleted), 2)
|
||||
require.Equal(t, len(fakeRepo.inserted), 2)
|
||||
require.Equal(t, len(fakeRepo.updated), 0)
|
||||
})
|
||||
})
|
||||
require.Equal(t, len(store.deleted), 2)
|
||||
require.Equal(t, len(store.inserted), 2)
|
||||
require.Equal(t, len(store.updated), 0)
|
||||
})
|
||||
|
||||
t.Run("Two configured datasource and purge others = false", func(t *testing.T) {
|
||||
setup()
|
||||
t.Run("two other datasources in database", func(t *testing.T) {
|
||||
fakeRepo.loadAll = []*models.DataSource{
|
||||
{Name: "Graphite", OrgId: 1, Id: 1},
|
||||
{Name: "old-graphite2", OrgId: 1, Id: 2},
|
||||
}
|
||||
store := &spyStore{items: []*models.DataSource{{Name: "Graphite", OrgId: 1, Id: 1}, {Name: "old-graphite2", OrgId: 1, Id: 2}}}
|
||||
orgStore := &mockOrgStore{}
|
||||
dc := newDatasourceProvisioner(logger, store, orgStore)
|
||||
err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
t.Run("should have two new datasources", func(t *testing.T) {
|
||||
dc := newDatasourceProvisioner(logger)
|
||||
err := dc.applyChanges(context.Background(), twoDatasourcesConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
}
|
||||
|
||||
require.Equal(t, len(fakeRepo.deleted), 0)
|
||||
require.Equal(t, len(fakeRepo.inserted), 1)
|
||||
require.Equal(t, len(fakeRepo.updated), 1)
|
||||
})
|
||||
})
|
||||
require.Equal(t, len(store.deleted), 0)
|
||||
require.Equal(t, len(store.inserted), 1)
|
||||
require.Equal(t, len(store.updated), 1)
|
||||
})
|
||||
|
||||
t.Run("broken yaml should return error", func(t *testing.T) {
|
||||
@ -202,14 +161,14 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("invalid access should warn about invalid value and return 'proxy'", func(t *testing.T) {
|
||||
reader := &configReader{log: logger}
|
||||
reader := &configReader{log: logger, orgStore: &mockOrgStore{}}
|
||||
configs, err := reader.readConfig(context.Background(), invalidAccess)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, configs[0].Datasources[0].Access, models.DS_ACCESS_PROXY)
|
||||
})
|
||||
|
||||
t.Run("skip invalid directory", func(t *testing.T) {
|
||||
cfgProvider := &configReader{log: log.New("test logger")}
|
||||
cfgProvider := &configReader{log: log.New("test logger"), orgStore: &mockOrgStore{}}
|
||||
cfg, err := cfgProvider.readConfig(context.Background(), "./invalid-directory")
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
@ -220,7 +179,7 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
|
||||
t.Run("can read all properties from version 1", func(t *testing.T) {
|
||||
_ = os.Setenv("TEST_VAR", "name")
|
||||
cfgProvider := &configReader{log: log.New("test logger")}
|
||||
cfgProvider := &configReader{log: log.New("test logger"), orgStore: &mockOrgStore{}}
|
||||
cfg, err := cfgProvider.readConfig(context.Background(), allProperties)
|
||||
_ = os.Unsetenv("TEST_VAR")
|
||||
if err != nil {
|
||||
@ -249,7 +208,7 @@ func TestDatasourceAsConfig(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("can read all properties from version 0", func(t *testing.T) {
|
||||
cfgProvider := &configReader{log: log.New("test logger")}
|
||||
cfgProvider := &configReader{log: log.New("test logger"), orgStore: &mockOrgStore{}}
|
||||
cfg, err := cfgProvider.readConfig(context.Background(), versionZero)
|
||||
if err != nil {
|
||||
t.Fatalf("readConfig return an error %v", err)
|
||||
@ -308,40 +267,41 @@ func validateDatasourceV1(t *testing.T, dsCfg *configs) {
|
||||
require.Equal(t, ds.UID, "test_uid")
|
||||
}
|
||||
|
||||
type fakeRepository struct {
|
||||
type mockOrgStore struct{ ExpectedOrg *models.Org }
|
||||
|
||||
func (m *mockOrgStore) GetOrgById(c context.Context, cmd *models.GetOrgByIdQuery) error {
|
||||
cmd.Result = m.ExpectedOrg
|
||||
return nil
|
||||
}
|
||||
|
||||
type spyStore struct {
|
||||
inserted []*models.AddDataSourceCommand
|
||||
deleted []*models.DeleteDataSourceCommand
|
||||
updated []*models.UpdateDataSourceCommand
|
||||
|
||||
loadAll []*models.DataSource
|
||||
items []*models.DataSource
|
||||
}
|
||||
|
||||
func mockDelete(ctx context.Context, cmd *models.DeleteDataSourceCommand) error {
|
||||
fakeRepo.deleted = append(fakeRepo.deleted, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func mockUpdate(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
|
||||
fakeRepo.updated = append(fakeRepo.updated, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func mockInsert(ctx context.Context, cmd *models.AddDataSourceCommand) error {
|
||||
fakeRepo.inserted = append(fakeRepo.inserted, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func mockGet(ctx context.Context, cmd *models.GetDataSourceQuery) error {
|
||||
for _, v := range fakeRepo.loadAll {
|
||||
if cmd.Name == v.Name && cmd.OrgId == v.OrgId {
|
||||
cmd.Result = v
|
||||
func (s *spyStore) GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error {
|
||||
for _, v := range s.items {
|
||||
if query.Name == v.Name && query.OrgId == v.OrgId {
|
||||
query.Result = v
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
return models.ErrDataSourceNotFound
|
||||
}
|
||||
|
||||
func mockGetOrg(ctx context.Context, _ *models.GetOrgByIdQuery) error {
|
||||
func (s *spyStore) DeleteDataSource(ctx context.Context, cmd *models.DeleteDataSourceCommand) error {
|
||||
s.deleted = append(s.deleted, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spyStore) AddDataSource(ctx context.Context, cmd *models.AddDataSourceCommand) error {
|
||||
s.inserted = append(s.inserted, cmd)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *spyStore) UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error {
|
||||
s.updated = append(s.updated, cmd)
|
||||
return nil
|
||||
}
|
||||
|
@ -4,13 +4,19 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/utils"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
GetDataSource(ctx context.Context, query *models.GetDataSourceQuery) error
|
||||
AddDataSource(ctx context.Context, cmd *models.AddDataSourceCommand) error
|
||||
UpdateDataSource(ctx context.Context, cmd *models.UpdateDataSourceCommand) error
|
||||
DeleteDataSource(ctx context.Context, cmd *models.DeleteDataSourceCommand) error
|
||||
}
|
||||
|
||||
var (
|
||||
// ErrInvalidConfigToManyDefault indicates that multiple datasource in the provisioning files
|
||||
// contains more than one datasource marked as default.
|
||||
@ -19,8 +25,8 @@ var (
|
||||
|
||||
// Provision scans a directory for provisioning config files
|
||||
// and provisions the datasource in those files.
|
||||
func Provision(ctx context.Context, configDirectory string) error {
|
||||
dc := newDatasourceProvisioner(log.New("provisioning.datasources"))
|
||||
func Provision(ctx context.Context, configDirectory string, store Store, orgStore utils.OrgStore) error {
|
||||
dc := newDatasourceProvisioner(log.New("provisioning.datasources"), store, orgStore)
|
||||
return dc.applyChanges(ctx, configDirectory)
|
||||
}
|
||||
|
||||
@ -29,12 +35,14 @@ func Provision(ctx context.Context, configDirectory string) error {
|
||||
type DatasourceProvisioner struct {
|
||||
log log.Logger
|
||||
cfgProvider *configReader
|
||||
store Store
|
||||
}
|
||||
|
||||
func newDatasourceProvisioner(log log.Logger) DatasourceProvisioner {
|
||||
func newDatasourceProvisioner(log log.Logger, store Store, orgStore utils.OrgStore) DatasourceProvisioner {
|
||||
return DatasourceProvisioner{
|
||||
log: log,
|
||||
cfgProvider: &configReader{log: log},
|
||||
cfgProvider: &configReader{log: log, orgStore: orgStore},
|
||||
store: store,
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +53,7 @@ func (dc *DatasourceProvisioner) apply(ctx context.Context, cfg *configs) error
|
||||
|
||||
for _, ds := range cfg.Datasources {
|
||||
cmd := &models.GetDataSourceQuery{OrgId: ds.OrgID, Name: ds.Name}
|
||||
err := bus.Dispatch(ctx, cmd)
|
||||
err := dc.store.GetDataSource(ctx, cmd)
|
||||
if err != nil && !errors.Is(err, models.ErrDataSourceNotFound) {
|
||||
return err
|
||||
}
|
||||
@ -53,13 +61,13 @@ func (dc *DatasourceProvisioner) apply(ctx context.Context, cfg *configs) error
|
||||
if errors.Is(err, models.ErrDataSourceNotFound) {
|
||||
insertCmd := createInsertCommand(ds)
|
||||
dc.log.Info("inserting datasource from configuration ", "name", insertCmd.Name, "uid", insertCmd.Uid)
|
||||
if err := bus.Dispatch(ctx, insertCmd); err != nil {
|
||||
if err := dc.store.AddDataSource(ctx, insertCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
updateCmd := createUpdateCommand(ds, cmd.Result.Id)
|
||||
dc.log.Debug("updating datasource from configuration", "name", updateCmd.Name, "uid", updateCmd.Uid)
|
||||
if err := bus.Dispatch(ctx, updateCmd); err != nil {
|
||||
if err := dc.store.UpdateDataSource(ctx, updateCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -86,7 +94,7 @@ func (dc *DatasourceProvisioner) applyChanges(ctx context.Context, configPath st
|
||||
func (dc *DatasourceProvisioner) deleteDatasources(ctx context.Context, dsToDelete []*deleteDatasourceConfig) error {
|
||||
for _, ds := range dsToDelete {
|
||||
cmd := &models.DeleteDataSourceCommand{OrgID: ds.OrgID, Name: ds.Name}
|
||||
if err := bus.Dispatch(ctx, cmd); err != nil {
|
||||
if err := dc.store.DeleteDataSource(ctx, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,6 @@
|
||||
package notifiers
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/encryption"
|
||||
@ -9,9 +8,18 @@ import (
|
||||
"golang.org/x/net/context"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
GetOrgById(c context.Context, cmd *models.GetOrgByIdQuery) error
|
||||
GetOrgByNameHandler(ctx context.Context, query *models.GetOrgByNameQuery) error
|
||||
GetAlertNotificationsWithUid(ctx context.Context, query *models.GetAlertNotificationsWithUidQuery) error
|
||||
DeleteAlertNotificationWithUid(ctx context.Context, cmd *models.DeleteAlertNotificationWithUidCommand) error
|
||||
CreateAlertNotificationCommand(ctx context.Context, cmd *models.CreateAlertNotificationCommand) error
|
||||
UpdateAlertNotificationWithUid(ctx context.Context, cmd *models.UpdateAlertNotificationWithUidCommand) error
|
||||
}
|
||||
|
||||
// Provision alert notifiers
|
||||
func Provision(ctx context.Context, configDirectory string, encryptionService encryption.Internal, notificationService *notifications.NotificationService) error {
|
||||
dc := newNotificationProvisioner(encryptionService, notificationService, log.New("provisioning.notifiers"))
|
||||
func Provision(ctx context.Context, configDirectory string, store Store, encryptionService encryption.Internal, notificationService *notifications.NotificationService) error {
|
||||
dc := newNotificationProvisioner(store, encryptionService, notificationService, log.New("provisioning.notifiers"))
|
||||
return dc.applyChanges(ctx, configDirectory)
|
||||
}
|
||||
|
||||
@ -19,15 +27,18 @@ func Provision(ctx context.Context, configDirectory string, encryptionService en
|
||||
type NotificationProvisioner struct {
|
||||
log log.Logger
|
||||
cfgProvider *configReader
|
||||
store Store
|
||||
}
|
||||
|
||||
func newNotificationProvisioner(encryptionService encryption.Internal, notifiationService *notifications.NotificationService, log log.Logger) NotificationProvisioner {
|
||||
func newNotificationProvisioner(store Store, encryptionService encryption.Internal, notifiationService *notifications.NotificationService, log log.Logger) NotificationProvisioner {
|
||||
return NotificationProvisioner{
|
||||
log: log,
|
||||
log: log,
|
||||
store: store,
|
||||
cfgProvider: &configReader{
|
||||
encryptionService: encryptionService,
|
||||
notificationService: notifiationService,
|
||||
log: log,
|
||||
orgStore: store,
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -50,7 +61,7 @@ func (dc *NotificationProvisioner) deleteNotifications(ctx context.Context, noti
|
||||
|
||||
if notification.OrgID == 0 && notification.OrgName != "" {
|
||||
getOrg := &models.GetOrgByNameQuery{Name: notification.OrgName}
|
||||
if err := bus.Dispatch(ctx, getOrg); err != nil {
|
||||
if err := dc.store.GetOrgByNameHandler(ctx, getOrg); err != nil {
|
||||
return err
|
||||
}
|
||||
notification.OrgID = getOrg.Result.Id
|
||||
@ -60,13 +71,13 @@ func (dc *NotificationProvisioner) deleteNotifications(ctx context.Context, noti
|
||||
|
||||
getNotification := &models.GetAlertNotificationsWithUidQuery{Uid: notification.UID, OrgId: notification.OrgID}
|
||||
|
||||
if err := bus.Dispatch(ctx, getNotification); err != nil {
|
||||
if err := dc.store.GetAlertNotificationsWithUid(ctx, getNotification); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if getNotification.Result != nil {
|
||||
cmd := &models.DeleteAlertNotificationWithUidCommand{Uid: getNotification.Result.Uid, OrgId: getNotification.OrgId}
|
||||
if err := bus.Dispatch(ctx, cmd); err != nil {
|
||||
if err := dc.store.DeleteAlertNotificationWithUid(ctx, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
@ -79,7 +90,7 @@ func (dc *NotificationProvisioner) mergeNotifications(ctx context.Context, notif
|
||||
for _, notification := range notificationToMerge {
|
||||
if notification.OrgID == 0 && notification.OrgName != "" {
|
||||
getOrg := &models.GetOrgByNameQuery{Name: notification.OrgName}
|
||||
if err := bus.Dispatch(ctx, getOrg); err != nil {
|
||||
if err := dc.store.GetOrgByNameHandler(ctx, getOrg); err != nil {
|
||||
return err
|
||||
}
|
||||
notification.OrgID = getOrg.Result.Id
|
||||
@ -88,7 +99,7 @@ func (dc *NotificationProvisioner) mergeNotifications(ctx context.Context, notif
|
||||
}
|
||||
|
||||
cmd := &models.GetAlertNotificationsWithUidQuery{OrgId: notification.OrgID, Uid: notification.UID}
|
||||
err := bus.Dispatch(ctx, cmd)
|
||||
err := dc.store.GetAlertNotificationsWithUid(ctx, cmd)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
@ -108,7 +119,7 @@ func (dc *NotificationProvisioner) mergeNotifications(ctx context.Context, notif
|
||||
SendReminder: notification.SendReminder,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(ctx, insertCmd); err != nil {
|
||||
if err := dc.store.CreateAlertNotificationCommand(ctx, insertCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
} else {
|
||||
@ -126,7 +137,7 @@ func (dc *NotificationProvisioner) mergeNotifications(ctx context.Context, notif
|
||||
SendReminder: notification.SendReminder,
|
||||
}
|
||||
|
||||
if err := bus.Dispatch(ctx, updateCmd); err != nil {
|
||||
if err := dc.store.UpdateAlertNotificationWithUid(ctx, updateCmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import (
|
||||
type configReader struct {
|
||||
encryptionService encryption.Internal
|
||||
notificationService *notifications.NotificationService
|
||||
orgStore utils.OrgStore
|
||||
log log.Logger
|
||||
}
|
||||
|
||||
@ -93,7 +94,7 @@ func (cr *configReader) checkOrgIDAndOrgName(ctx context.Context, notifications
|
||||
notification.OrgID = 0
|
||||
}
|
||||
} else {
|
||||
if err := utils.CheckOrgExists(ctx, notification.OrgID); err != nil {
|
||||
if err := utils.CheckOrgExists(ctx, cr.orgStore, notification.OrgID); err != nil {
|
||||
return fmt.Errorf("failed to provision %q notification: %w", notification.Name, err)
|
||||
}
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
setup()
|
||||
_ = os.Setenv("TEST_VAR", "default")
|
||||
cfgProvider := &configReader{
|
||||
orgStore: sqlStore,
|
||||
encryptionService: ossencryption.ProvideService(),
|
||||
log: log.New("test logger"),
|
||||
}
|
||||
@ -139,7 +140,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
t.Run("One configured notification", func(t *testing.T) {
|
||||
t.Run("no notification in database", func(t *testing.T) {
|
||||
setup()
|
||||
dc := newNotificationProvisioner(ossencryption.ProvideService(), nil, logger)
|
||||
dc := newNotificationProvisioner(sqlStore, ossencryption.ProvideService(), nil, logger)
|
||||
|
||||
err := dc.applyChanges(context.Background(), twoNotificationsConfig)
|
||||
if err != nil {
|
||||
@ -170,7 +171,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
require.Equal(t, len(notificationsQuery.Result), 1)
|
||||
|
||||
t.Run("should update one notification", func(t *testing.T) {
|
||||
dc := newNotificationProvisioner(ossencryption.ProvideService(), nil, logger)
|
||||
dc := newNotificationProvisioner(sqlStore, ossencryption.ProvideService(), nil, logger)
|
||||
err = dc.applyChanges(context.Background(), twoNotificationsConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
@ -194,7 +195,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
})
|
||||
t.Run("Two notifications with is_default", func(t *testing.T) {
|
||||
setup()
|
||||
dc := newNotificationProvisioner(ossencryption.ProvideService(), nil, logger)
|
||||
dc := newNotificationProvisioner(sqlStore, ossencryption.ProvideService(), nil, logger)
|
||||
err := dc.applyChanges(context.Background(), doubleNotificationsConfig)
|
||||
t.Run("should both be inserted", func(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
@ -237,7 +238,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
require.Equal(t, len(notificationsQuery.Result), 2)
|
||||
|
||||
t.Run("should have two new notifications", func(t *testing.T) {
|
||||
dc := newNotificationProvisioner(ossencryption.ProvideService(), nil, logger)
|
||||
dc := newNotificationProvisioner(sqlStore, ossencryption.ProvideService(), nil, logger)
|
||||
err := dc.applyChanges(context.Background(), twoNotificationsConfig)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
@ -254,11 +255,11 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
t.Run("Can read correct properties with orgName instead of orgId", func(t *testing.T) {
|
||||
setup()
|
||||
existingOrg1 := models.GetOrgByNameQuery{Name: "Main Org. 1"}
|
||||
err := sqlstore.GetOrgByName(context.Background(), &existingOrg1)
|
||||
err := sqlStore.GetOrgByNameHandler(context.Background(), &existingOrg1)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, existingOrg1.Result)
|
||||
existingOrg2 := models.GetOrgByNameQuery{Name: "Main Org. 2"}
|
||||
err = sqlstore.GetOrgByName(context.Background(), &existingOrg2)
|
||||
err = sqlStore.GetOrgByNameHandler(context.Background(), &existingOrg2)
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, existingOrg2.Result)
|
||||
|
||||
@ -271,7 +272,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
err = sqlStore.CreateAlertNotificationCommand(context.Background(), &existingNotificationCmd)
|
||||
require.NoError(t, err)
|
||||
|
||||
dc := newNotificationProvisioner(ossencryption.ProvideService(), nil, logger)
|
||||
dc := newNotificationProvisioner(sqlStore, ossencryption.ProvideService(), nil, logger)
|
||||
err = dc.applyChanges(context.Background(), correctPropertiesWithOrgName)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
@ -290,7 +291,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
|
||||
t.Run("Config doesn't contain required field", func(t *testing.T) {
|
||||
setup()
|
||||
dc := newNotificationProvisioner(ossencryption.ProvideService(), nil, logger)
|
||||
dc := newNotificationProvisioner(sqlStore, ossencryption.ProvideService(), nil, logger)
|
||||
err := dc.applyChanges(context.Background(), noRequiredFields)
|
||||
require.NotNil(t, err)
|
||||
|
||||
@ -304,7 +305,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
t.Run("Empty yaml file", func(t *testing.T) {
|
||||
t.Run("should have not changed repo", func(t *testing.T) {
|
||||
setup()
|
||||
dc := newNotificationProvisioner(ossencryption.ProvideService(), nil, logger)
|
||||
dc := newNotificationProvisioner(sqlStore, ossencryption.ProvideService(), nil, logger)
|
||||
err := dc.applyChanges(context.Background(), emptyFile)
|
||||
if err != nil {
|
||||
t.Fatalf("applyChanges return an error %v", err)
|
||||
@ -318,6 +319,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
|
||||
t.Run("Broken yaml should return error", func(t *testing.T) {
|
||||
reader := &configReader{
|
||||
orgStore: sqlStore,
|
||||
encryptionService: ossencryption.ProvideService(),
|
||||
log: log.New("test logger"),
|
||||
}
|
||||
@ -328,6 +330,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
|
||||
t.Run("Skip invalid directory", func(t *testing.T) {
|
||||
cfgProvider := &configReader{
|
||||
orgStore: sqlStore,
|
||||
encryptionService: ossencryption.ProvideService(),
|
||||
log: log.New("test logger"),
|
||||
}
|
||||
@ -341,6 +344,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
|
||||
t.Run("Unknown notifier should return error", func(t *testing.T) {
|
||||
cfgProvider := &configReader{
|
||||
orgStore: sqlStore,
|
||||
encryptionService: ossencryption.ProvideService(),
|
||||
log: log.New("test logger"),
|
||||
}
|
||||
@ -351,6 +355,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
|
||||
t.Run("Read incorrect properties", func(t *testing.T) {
|
||||
cfgProvider := &configReader{
|
||||
orgStore: sqlStore,
|
||||
encryptionService: ossencryption.ProvideService(),
|
||||
log: log.New("test logger"),
|
||||
}
|
||||
@ -363,7 +368,7 @@ func TestNotificationAsConfig(t *testing.T) {
|
||||
|
||||
func setupBusHandlers(sqlStore *sqlstore.SQLStore) {
|
||||
bus.AddHandler("getOrg", func(ctx context.Context, q *models.GetOrgByNameQuery) error {
|
||||
return sqlstore.GetOrgByName(ctx, q)
|
||||
return sqlStore.GetOrgByNameHandler(ctx, q)
|
||||
})
|
||||
|
||||
bus.AddHandler("getAlertNotifications", func(ctx context.Context, q *models.GetAlertNotificationsWithUidQuery) error {
|
||||
|
57
pkg/services/provisioning/plugins/mocks/Store.go
Normal file
57
pkg/services/provisioning/plugins/mocks/Store.go
Normal file
@ -0,0 +1,57 @@
|
||||
// Code generated by mockery v2.10.0. DO NOT EDIT.
|
||||
|
||||
package mocks
|
||||
|
||||
import (
|
||||
context "context"
|
||||
|
||||
models "github.com/grafana/grafana/pkg/models"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// Store is an autogenerated mock type for the Store type
|
||||
type Store struct {
|
||||
mock.Mock
|
||||
}
|
||||
|
||||
// GetOrgByNameHandler provides a mock function with given fields: ctx, query
|
||||
func (_m *Store) GetOrgByNameHandler(ctx context.Context, query *models.GetOrgByNameQuery) error {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.GetOrgByNameQuery) error); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// GetPluginSettingById provides a mock function with given fields: ctx, query
|
||||
func (_m *Store) GetPluginSettingById(ctx context.Context, query *models.GetPluginSettingByIdQuery) error {
|
||||
ret := _m.Called(ctx, query)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.GetPluginSettingByIdQuery) error); ok {
|
||||
r0 = rf(ctx, query)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
||||
|
||||
// UpdatePluginSetting provides a mock function with given fields: ctx, cmd
|
||||
func (_m *Store) UpdatePluginSetting(ctx context.Context, cmd *models.UpdatePluginSettingCmd) error {
|
||||
ret := _m.Called(ctx, cmd)
|
||||
|
||||
var r0 error
|
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.UpdatePluginSettingCmd) error); ok {
|
||||
r0 = rf(ctx, cmd)
|
||||
} else {
|
||||
r0 = ret.Error(0)
|
||||
}
|
||||
|
||||
return r0
|
||||
}
|
@ -4,19 +4,25 @@ import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
)
|
||||
|
||||
type Store interface {
|
||||
GetOrgByNameHandler(ctx context.Context, query *models.GetOrgByNameQuery) error
|
||||
GetPluginSettingById(ctx context.Context, query *models.GetPluginSettingByIdQuery) error
|
||||
UpdatePluginSetting(ctx context.Context, cmd *models.UpdatePluginSettingCmd) error
|
||||
}
|
||||
|
||||
// Provision scans a directory for provisioning config files
|
||||
// and provisions the app in those files.
|
||||
func Provision(ctx context.Context, configDirectory string, pluginStore plugins.Store) error {
|
||||
func Provision(ctx context.Context, configDirectory string, store Store, pluginStore plugins.Store) error {
|
||||
logger := log.New("provisioning.plugins")
|
||||
ap := PluginProvisioner{
|
||||
log: logger,
|
||||
cfgProvider: newConfigReader(logger, pluginStore),
|
||||
store: store,
|
||||
}
|
||||
return ap.applyChanges(ctx, configDirectory)
|
||||
}
|
||||
@ -26,13 +32,14 @@ func Provision(ctx context.Context, configDirectory string, pluginStore plugins.
|
||||
type PluginProvisioner struct {
|
||||
log log.Logger
|
||||
cfgProvider configReader
|
||||
store Store
|
||||
}
|
||||
|
||||
func (ap *PluginProvisioner) apply(ctx context.Context, cfg *pluginsAsConfig) error {
|
||||
for _, app := range cfg.Apps {
|
||||
if app.OrgID == 0 && app.OrgName != "" {
|
||||
getOrgQuery := &models.GetOrgByNameQuery{Name: app.OrgName}
|
||||
if err := bus.Dispatch(ctx, getOrgQuery); err != nil {
|
||||
if err := ap.store.GetOrgByNameHandler(ctx, getOrgQuery); err != nil {
|
||||
return err
|
||||
}
|
||||
app.OrgID = getOrgQuery.Result.Id
|
||||
@ -41,7 +48,7 @@ func (ap *PluginProvisioner) apply(ctx context.Context, cfg *pluginsAsConfig) er
|
||||
}
|
||||
|
||||
query := &models.GetPluginSettingByIdQuery{OrgId: app.OrgID, PluginId: app.PluginID}
|
||||
err := bus.Dispatch(ctx, query)
|
||||
err := ap.store.GetPluginSettingById(ctx, query)
|
||||
if err != nil {
|
||||
if !errors.Is(err, models.ErrPluginSettingNotFound) {
|
||||
return err
|
||||
@ -60,7 +67,7 @@ func (ap *PluginProvisioner) apply(ctx context.Context, cfg *pluginsAsConfig) er
|
||||
SecureJsonData: app.SecureJSONData,
|
||||
PluginVersion: app.PluginVersion,
|
||||
}
|
||||
if err := bus.Dispatch(ctx, cmd); err != nil {
|
||||
if err := ap.store.UpdatePluginSetting(ctx, cmd); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
@ -5,7 +5,6 @@ import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/stretchr/testify/require"
|
||||
@ -21,32 +20,6 @@ func TestPluginProvisioner(t *testing.T) {
|
||||
})
|
||||
|
||||
t.Run("Should apply configurations", func(t *testing.T) {
|
||||
bus.AddHandler("test", func(ctx context.Context, query *models.GetOrgByNameQuery) error {
|
||||
if query.Name == "Org 4" {
|
||||
query.Result = &models.Org{Id: 4}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
|
||||
bus.AddHandler("test", func(ctx context.Context, query *models.GetPluginSettingByIdQuery) error {
|
||||
if query.PluginId == "test-plugin" && query.OrgId == 2 {
|
||||
query.Result = &models.PluginSetting{
|
||||
PluginVersion: "2.0.1",
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return models.ErrPluginSettingNotFound
|
||||
})
|
||||
|
||||
sentCommands := []*models.UpdatePluginSettingCmd{}
|
||||
|
||||
bus.AddHandler("test", func(ctx context.Context, cmd *models.UpdatePluginSettingCmd) error {
|
||||
sentCommands = append(sentCommands, cmd)
|
||||
return nil
|
||||
})
|
||||
|
||||
cfg := []*pluginsAsConfig{
|
||||
{
|
||||
Apps: []*appFromConfig{
|
||||
@ -58,11 +31,12 @@ func TestPluginProvisioner(t *testing.T) {
|
||||
},
|
||||
}
|
||||
reader := &testConfigReader{result: cfg}
|
||||
ap := PluginProvisioner{log: log.New("test"), cfgProvider: reader}
|
||||
store := &mockStore{}
|
||||
ap := PluginProvisioner{log: log.New("test"), cfgProvider: reader, store: store}
|
||||
|
||||
err := ap.applyChanges(context.Background(), "")
|
||||
require.NoError(t, err)
|
||||
require.Len(t, sentCommands, 4)
|
||||
require.Len(t, store.sentCommands, 4)
|
||||
|
||||
testCases := []struct {
|
||||
ExpectedPluginID string
|
||||
@ -77,7 +51,7 @@ func TestPluginProvisioner(t *testing.T) {
|
||||
}
|
||||
|
||||
for index, tc := range testCases {
|
||||
cmd := sentCommands[index]
|
||||
cmd := store.sentCommands[index]
|
||||
require.NotNil(t, cmd)
|
||||
require.Equal(t, tc.ExpectedPluginID, cmd.PluginId)
|
||||
require.Equal(t, tc.ExpectedOrgID, cmd.OrgId)
|
||||
@ -95,3 +69,30 @@ type testConfigReader struct {
|
||||
func (tcr *testConfigReader) readConfig(ctx context.Context, path string) ([]*pluginsAsConfig, error) {
|
||||
return tcr.result, tcr.err
|
||||
}
|
||||
|
||||
type mockStore struct {
|
||||
sentCommands []*models.UpdatePluginSettingCmd
|
||||
}
|
||||
|
||||
func (m *mockStore) GetOrgByNameHandler(ctx context.Context, query *models.GetOrgByNameQuery) error {
|
||||
if query.Name == "Org 4" {
|
||||
query.Result = &models.Org{Id: 4}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (m *mockStore) GetPluginSettingById(ctx context.Context, query *models.GetPluginSettingByIdQuery) error {
|
||||
if query.PluginId == "test-plugin" && query.OrgId == 2 {
|
||||
query.Result = &models.PluginSetting{
|
||||
PluginVersion: "2.0.1",
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
return models.ErrPluginSettingNotFound
|
||||
}
|
||||
|
||||
func (m *mockStore) UpdatePluginSetting(ctx context.Context, cmd *models.UpdatePluginSettingCmd) error {
|
||||
m.sentCommands = append(m.sentCommands, cmd)
|
||||
return nil
|
||||
}
|
||||
|
@ -15,6 +15,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/notifiers"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/utils"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/grafana/grafana/pkg/util/errutil"
|
||||
@ -26,6 +27,7 @@ func ProvideService(cfg *setting.Cfg, sqlStore *sqlstore.SQLStore, pluginStore p
|
||||
) (*ProvisioningServiceImpl, error) {
|
||||
s := &ProvisioningServiceImpl{
|
||||
Cfg: cfg,
|
||||
SQLStore: sqlStore,
|
||||
pluginStore: pluginStore,
|
||||
EncryptionService: encryptionService,
|
||||
NotificationService: notificatonService,
|
||||
@ -64,9 +66,9 @@ func NewProvisioningServiceImpl() *ProvisioningServiceImpl {
|
||||
// Used for testing purposes
|
||||
func newProvisioningServiceImpl(
|
||||
newDashboardProvisioner dashboards.DashboardProvisionerFactory,
|
||||
provisionNotifiers func(context.Context, string, encryption.Internal, *notifications.NotificationService) error,
|
||||
provisionDatasources func(context.Context, string) error,
|
||||
provisionPlugins func(context.Context, string, plugifaces.Store) error,
|
||||
provisionNotifiers func(context.Context, string, notifiers.Store, encryption.Internal, *notifications.NotificationService) error,
|
||||
provisionDatasources func(context.Context, string, datasources.Store, utils.OrgStore) error,
|
||||
provisionPlugins func(context.Context, string, plugins.Store, plugifaces.Store) error,
|
||||
) *ProvisioningServiceImpl {
|
||||
return &ProvisioningServiceImpl{
|
||||
log: log.New("provisioning"),
|
||||
@ -87,9 +89,9 @@ type ProvisioningServiceImpl struct {
|
||||
pollingCtxCancel context.CancelFunc
|
||||
newDashboardProvisioner dashboards.DashboardProvisionerFactory
|
||||
dashboardProvisioner dashboards.DashboardProvisioner
|
||||
provisionNotifiers func(context.Context, string, encryption.Internal, *notifications.NotificationService) error
|
||||
provisionDatasources func(context.Context, string) error
|
||||
provisionPlugins func(context.Context, string, plugifaces.Store) error
|
||||
provisionNotifiers func(context.Context, string, notifiers.Store, encryption.Internal, *notifications.NotificationService) error
|
||||
provisionDatasources func(context.Context, string, datasources.Store, utils.OrgStore) error
|
||||
provisionPlugins func(context.Context, string, plugins.Store, plugifaces.Store) error
|
||||
mutex sync.Mutex
|
||||
dashboardService dashboardservice.DashboardProvisioningService
|
||||
}
|
||||
@ -144,7 +146,7 @@ func (ps *ProvisioningServiceImpl) Run(ctx context.Context) error {
|
||||
|
||||
func (ps *ProvisioningServiceImpl) ProvisionDatasources(ctx context.Context) error {
|
||||
datasourcePath := filepath.Join(ps.Cfg.ProvisioningPath, "datasources")
|
||||
if err := ps.provisionDatasources(ctx, datasourcePath); err != nil {
|
||||
if err := ps.provisionDatasources(ctx, datasourcePath, ps.SQLStore, ps.SQLStore); err != nil {
|
||||
err = errutil.Wrap("Datasource provisioning error", err)
|
||||
ps.log.Error("Failed to provision data sources", "error", err)
|
||||
return err
|
||||
@ -154,7 +156,7 @@ func (ps *ProvisioningServiceImpl) ProvisionDatasources(ctx context.Context) err
|
||||
|
||||
func (ps *ProvisioningServiceImpl) ProvisionPlugins(ctx context.Context) error {
|
||||
appPath := filepath.Join(ps.Cfg.ProvisioningPath, "plugins")
|
||||
if err := ps.provisionPlugins(ctx, appPath, ps.pluginStore); err != nil {
|
||||
if err := ps.provisionPlugins(ctx, appPath, ps.SQLStore, ps.pluginStore); err != nil {
|
||||
err = errutil.Wrap("app provisioning error", err)
|
||||
ps.log.Error("Failed to provision plugins", "error", err)
|
||||
return err
|
||||
@ -164,7 +166,7 @@ func (ps *ProvisioningServiceImpl) ProvisionPlugins(ctx context.Context) error {
|
||||
|
||||
func (ps *ProvisioningServiceImpl) ProvisionNotifications(ctx context.Context) error {
|
||||
alertNotificationsPath := filepath.Join(ps.Cfg.ProvisioningPath, "notifiers")
|
||||
if err := ps.provisionNotifiers(ctx, alertNotificationsPath, ps.EncryptionService, ps.NotificationService); err != nil {
|
||||
if err := ps.provisionNotifiers(ctx, alertNotificationsPath, ps.SQLStore, ps.EncryptionService, ps.NotificationService); err != nil {
|
||||
err = errutil.Wrap("Alert notification provisioning error", err)
|
||||
ps.log.Error("Failed to provision alert notifications", "error", err)
|
||||
return err
|
||||
@ -174,7 +176,7 @@ func (ps *ProvisioningServiceImpl) ProvisionNotifications(ctx context.Context) e
|
||||
|
||||
func (ps *ProvisioningServiceImpl) ProvisionDashboards(ctx context.Context) error {
|
||||
dashboardPath := filepath.Join(ps.Cfg.ProvisioningPath, "dashboards")
|
||||
dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.dashboardService)
|
||||
dashProvisioner, err := ps.newDashboardProvisioner(ctx, dashboardPath, ps.dashboardService, ps.SQLStore)
|
||||
if err != nil {
|
||||
return errutil.Wrap("Failed to create provisioner", err)
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
|
||||
dashboardstore "github.com/grafana/grafana/pkg/services/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/dashboards"
|
||||
"github.com/grafana/grafana/pkg/services/provisioning/utils"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
@ -92,7 +93,7 @@ func setup() *serviceTestStruct {
|
||||
}
|
||||
|
||||
serviceTest.service = newProvisioningServiceImpl(
|
||||
func(context.Context, string, dashboardstore.DashboardProvisioningService) (dashboards.DashboardProvisioner, error) {
|
||||
func(context.Context, string, dashboardstore.DashboardProvisioningService, utils.OrgStore) (dashboards.DashboardProvisioner, error) {
|
||||
return serviceTest.mock, nil
|
||||
},
|
||||
nil,
|
||||
|
@ -5,13 +5,16 @@ import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func CheckOrgExists(ctx context.Context, orgID int64) error {
|
||||
type OrgStore interface {
|
||||
GetOrgById(context.Context, *models.GetOrgByIdQuery) error
|
||||
}
|
||||
|
||||
func CheckOrgExists(ctx context.Context, store OrgStore, orgID int64) error {
|
||||
query := models.GetOrgByIdQuery{Id: orgID}
|
||||
if err := bus.Dispatch(ctx, &query); err != nil {
|
||||
if err := store.GetOrgById(ctx, &query); err != nil {
|
||||
if errors.Is(err, models.ErrOrgNotFound) {
|
||||
return err
|
||||
}
|
||||
|
@ -1,32 +1 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
"github.com/grafana/grafana/pkg/services/sqlstore"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestCheckOrgExists(t *testing.T) {
|
||||
t.Run("with default org in database", func(t *testing.T) {
|
||||
sqlstore.InitTestDB(t)
|
||||
|
||||
defaultOrg := models.CreateOrgCommand{Name: "Main Org."}
|
||||
|
||||
err := sqlstore.CreateOrg(context.Background(), &defaultOrg)
|
||||
require.NoError(t, err)
|
||||
|
||||
t.Run("default org exists", func(t *testing.T) {
|
||||
err := CheckOrgExists(context.Background(), defaultOrg.Result.Id)
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
t.Run("other org doesn't exist", func(t *testing.T) {
|
||||
err := CheckOrgExists(context.Background(), defaultOrg.Result.Id+1)
|
||||
require.Equal(t, err, models.ErrOrgNotFound)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1,43 +1 @@
|
||||
package sqlstore
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/models"
|
||||
)
|
||||
|
||||
func (ss *SQLStore) addDashboardProvisioningQueryAndCommandHandlers() {
|
||||
bus.AddHandler("sql", ss.DeleteOrphanedProvisionedDashboards)
|
||||
}
|
||||
|
||||
type DashboardExtras struct {
|
||||
Id int64
|
||||
DashboardId int64
|
||||
Key string
|
||||
Value string
|
||||
}
|
||||
|
||||
func (ss *SQLStore) DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error {
|
||||
var result []*models.DashboardProvisioning
|
||||
|
||||
convertedReaderNames := make([]interface{}, len(cmd.ReaderNames))
|
||||
for index, readerName := range cmd.ReaderNames {
|
||||
convertedReaderNames[index] = readerName
|
||||
}
|
||||
|
||||
err := x.NotIn("name", convertedReaderNames...).Find(&result)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, deleteDashCommand := range result {
|
||||
err := ss.DeleteDashboard(ctx, &models.DeleteDashboardCommand{Id: deleteDashCommand.DashboardId})
|
||||
if err != nil && !errors.Is(err, models.ErrDashboardNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
@ -13,14 +13,16 @@ type OrgListResponse []struct {
|
||||
Response error
|
||||
}
|
||||
type SQLStoreMock struct {
|
||||
LastGetAlertsQuery *models.GetAlertsQuery
|
||||
LatestUserId int64
|
||||
LastGetAlertsQuery *models.GetAlertsQuery
|
||||
LatestUserId int64
|
||||
|
||||
ExpectedUser *models.User
|
||||
ExpectedDatasource *models.DataSource
|
||||
ExpectedAlert *models.Alert
|
||||
ExpectedPluginSetting *models.PluginSetting
|
||||
ExpectedDashboard *models.Dashboard
|
||||
ExpectedDashboards []*models.Dashboard
|
||||
ExpectedDashboardVersion *models.DashboardVersion
|
||||
ExpectedDashboardVersions []*models.DashboardVersion
|
||||
ExpectedDashboardAclInfoList []*models.DashboardAclInfoDTO
|
||||
ExpectedUserOrgList []*models.UserOrgDTO
|
||||
@ -92,10 +94,19 @@ func (m *SQLStoreMock) SearchDashboardSnapshots(query *models.GetDashboardSnapsh
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) GetOrgById(ctx context.Context, cmd *models.GetOrgByIdQuery) error {
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) GetOrgByName(name string) (*models.Org, error) {
|
||||
return m.ExpectedOrg, m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) GetOrgByNameHandler(ctx context.Context, query *models.GetOrgByNameQuery) error {
|
||||
query.Result = m.ExpectedOrg
|
||||
return m.ExpectedError
|
||||
}
|
||||
|
||||
func (m *SQLStoreMock) CreateOrgWithMember(name string, userID int64) (models.Org, error) {
|
||||
return *m.ExpectedOrg, nil
|
||||
}
|
||||
|
@ -16,11 +16,11 @@ import (
|
||||
const MainOrgName = "Main Org."
|
||||
|
||||
func (ss *SQLStore) addOrgQueryAndCommandHandlers() {
|
||||
bus.AddHandler("sql", GetOrgById)
|
||||
bus.AddHandler("sql", ss.GetOrgById)
|
||||
bus.AddHandler("sql", CreateOrg)
|
||||
bus.AddHandler("sql", ss.UpdateOrg)
|
||||
bus.AddHandler("sql", ss.UpdateOrgAddress)
|
||||
bus.AddHandler("sql", GetOrgByName)
|
||||
bus.AddHandler("sql", ss.GetOrgByNameHandler)
|
||||
bus.AddHandler("sql", ss.SearchOrgs)
|
||||
bus.AddHandler("sql", ss.DeleteOrg)
|
||||
}
|
||||
@ -48,7 +48,7 @@ func (ss *SQLStore) SearchOrgs(ctx context.Context, query *models.SearchOrgsQuer
|
||||
return err
|
||||
}
|
||||
|
||||
func GetOrgById(ctx context.Context, query *models.GetOrgByIdQuery) error {
|
||||
func (ss *SQLStore) GetOrgById(ctx context.Context, query *models.GetOrgByIdQuery) error {
|
||||
var org models.Org
|
||||
exists, err := x.Id(query.Id).Get(&org)
|
||||
if err != nil {
|
||||
@ -63,7 +63,7 @@ func GetOrgById(ctx context.Context, query *models.GetOrgByIdQuery) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetOrgByName(ctx context.Context, query *models.GetOrgByNameQuery) error {
|
||||
func (ss *SQLStore) GetOrgByNameHandler(ctx context.Context, query *models.GetOrgByNameQuery) error {
|
||||
var org models.Org
|
||||
exists, err := x.Where("name=?", query.Name).Get(&org)
|
||||
if err != nil {
|
||||
|
@ -130,7 +130,6 @@ func newSQLStore(cfg *setting.Cfg, cacheService *localcache.CacheService, b bus.
|
||||
ss.addPlaylistQueryAndCommandHandlers()
|
||||
ss.addLoginAttemptQueryAndCommandHandlers()
|
||||
ss.addTeamQueryAndCommandHandlers()
|
||||
ss.addDashboardProvisioningQueryAndCommandHandlers()
|
||||
ss.addOrgQueryAndCommandHandlers()
|
||||
|
||||
bus.AddHandler("sql", ss.GetDBHealthQuery)
|
||||
|
@ -79,7 +79,7 @@ func populateDB(t *testing.T, sqlStore *SQLStore) {
|
||||
|
||||
// get 1st user's organisation
|
||||
getOrgByIdQuery := &models.GetOrgByIdQuery{Id: users[0].OrgId}
|
||||
err := GetOrgById(context.Background(), getOrgByIdQuery)
|
||||
err := sqlStore.GetOrgById(context.Background(), getOrgByIdQuery)
|
||||
require.NoError(t, err)
|
||||
org := getOrgByIdQuery.Result
|
||||
|
||||
@ -103,7 +103,7 @@ func populateDB(t *testing.T, sqlStore *SQLStore) {
|
||||
|
||||
// get 2nd user's organisation
|
||||
getOrgByIdQuery = &models.GetOrgByIdQuery{Id: users[1].OrgId}
|
||||
err = GetOrgById(context.Background(), getOrgByIdQuery)
|
||||
err = sqlStore.GetOrgById(context.Background(), getOrgByIdQuery)
|
||||
require.NoError(t, err)
|
||||
org = getOrgByIdQuery.Result
|
||||
|
||||
|
@ -24,7 +24,8 @@ type Store interface {
|
||||
UpdateOrg(ctx context.Context, cmd *models.UpdateOrgCommand) error
|
||||
UpdateOrgAddress(ctx context.Context, cmd *models.UpdateOrgAddressCommand) error
|
||||
DeleteOrg(ctx context.Context, cmd *models.DeleteOrgCommand) error
|
||||
DeleteOrphanedProvisionedDashboards(ctx context.Context, cmd *models.DeleteOrphanedProvisionedDashboardsCommand) error
|
||||
GetOrgById(context.Context, *models.GetOrgByIdQuery) error
|
||||
GetOrgByNameHandler(ctx context.Context, query *models.GetOrgByNameQuery) error
|
||||
CreateLoginAttempt(ctx context.Context, cmd *models.CreateLoginAttemptCommand) error
|
||||
DeleteOldLoginAttempts(ctx context.Context, cmd *models.DeleteOldLoginAttemptsCommand) error
|
||||
CloneUserToServiceAccount(ctx context.Context, siUser *models.SignedInUser) (*models.User, error)
|
||||
|
Loading…
Reference in New Issue
Block a user