mirror of https://github.com/grafana/grafana
Alerting: Add provisioning GET routes for message templates (#48367)
* Template service * Add GET routes and implement them * Generate mock for persist layer * Unit tests for reading templates * Set up composition root and get integration tests working * Fix prealloc issue * Extract setup boilerplate * Update AuthorizationTest * Rebase and resolve * Fix linter errorpull/48474/head
parent
d4616cfe26
commit
735822e48a
@ -0,0 +1,24 @@ |
||||
package definitions |
||||
|
||||
// swagger:route GET /api/provisioning/templates provisioning RouteGetTemplates
|
||||
//
|
||||
// Get all message templates.
|
||||
//
|
||||
// Responses:
|
||||
// 200: []MessageTemplate
|
||||
// 400: ValidationError
|
||||
|
||||
// swagger:route GET /api/provisioning/templates/{ID} provisioning RouteGetTemplate
|
||||
//
|
||||
// Get a message template.
|
||||
//
|
||||
// Responses:
|
||||
// 200: MessageTemplate
|
||||
// 404: NotFound
|
||||
|
||||
type MessageTemplate struct { |
||||
Name string |
||||
Template string |
||||
} |
||||
|
||||
type NotFound struct{} |
@ -0,0 +1,111 @@ |
||||
// Code generated by mockery v2.12.0. DO NOT EDIT.
|
||||
|
||||
package provisioning |
||||
|
||||
import ( |
||||
context "context" |
||||
|
||||
models "github.com/grafana/grafana/pkg/services/ngalert/models" |
||||
mock "github.com/stretchr/testify/mock" |
||||
|
||||
testing "testing" |
||||
) |
||||
|
||||
// MockAMConfigStore is an autogenerated mock type for the AMConfigStore type
|
||||
type MockAMConfigStore struct { |
||||
mock.Mock |
||||
} |
||||
|
||||
type MockAMConfigStore_Expecter struct { |
||||
mock *mock.Mock |
||||
} |
||||
|
||||
func (_m *MockAMConfigStore) EXPECT() *MockAMConfigStore_Expecter { |
||||
return &MockAMConfigStore_Expecter{mock: &_m.Mock} |
||||
} |
||||
|
||||
// GetLatestAlertmanagerConfiguration provides a mock function with given fields: ctx, query
|
||||
func (_m *MockAMConfigStore) GetLatestAlertmanagerConfiguration(ctx context.Context, query *models.GetLatestAlertmanagerConfigurationQuery) error { |
||||
ret := _m.Called(ctx, query) |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.GetLatestAlertmanagerConfigurationQuery) error); ok { |
||||
r0 = rf(ctx, query) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'GetLatestAlertmanagerConfiguration'
|
||||
type MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// GetLatestAlertmanagerConfiguration is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - query *models.GetLatestAlertmanagerConfigurationQuery
|
||||
func (_e *MockAMConfigStore_Expecter) GetLatestAlertmanagerConfiguration(ctx interface{}, query interface{}) *MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call { |
||||
return &MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call{Call: _e.mock.On("GetLatestAlertmanagerConfiguration", ctx, query)} |
||||
} |
||||
|
||||
func (_c *MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call) Run(run func(ctx context.Context, query *models.GetLatestAlertmanagerConfigurationQuery)) *MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(*models.GetLatestAlertmanagerConfigurationQuery)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call) Return(_a0 error) *MockAMConfigStore_GetLatestAlertmanagerConfiguration_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
// UpdateAlertmanagerConfiguration provides a mock function with given fields: ctx, cmd
|
||||
func (_m *MockAMConfigStore) UpdateAlertmanagerConfiguration(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error { |
||||
ret := _m.Called(ctx, cmd) |
||||
|
||||
var r0 error |
||||
if rf, ok := ret.Get(0).(func(context.Context, *models.SaveAlertmanagerConfigurationCmd) error); ok { |
||||
r0 = rf(ctx, cmd) |
||||
} else { |
||||
r0 = ret.Error(0) |
||||
} |
||||
|
||||
return r0 |
||||
} |
||||
|
||||
// MockAMConfigStore_UpdateAlertmanagerConfiguration_Call is a *mock.Call that shadows Run/Return methods with type explicit version for method 'UpdateAlertmanagerConfiguration'
|
||||
type MockAMConfigStore_UpdateAlertmanagerConfiguration_Call struct { |
||||
*mock.Call |
||||
} |
||||
|
||||
// UpdateAlertmanagerConfiguration is a helper method to define mock.On call
|
||||
// - ctx context.Context
|
||||
// - cmd *models.SaveAlertmanagerConfigurationCmd
|
||||
func (_e *MockAMConfigStore_Expecter) UpdateAlertmanagerConfiguration(ctx interface{}, cmd interface{}) *MockAMConfigStore_UpdateAlertmanagerConfiguration_Call { |
||||
return &MockAMConfigStore_UpdateAlertmanagerConfiguration_Call{Call: _e.mock.On("UpdateAlertmanagerConfiguration", ctx, cmd)} |
||||
} |
||||
|
||||
func (_c *MockAMConfigStore_UpdateAlertmanagerConfiguration_Call) Run(run func(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd)) *MockAMConfigStore_UpdateAlertmanagerConfiguration_Call { |
||||
_c.Call.Run(func(args mock.Arguments) { |
||||
run(args[0].(context.Context), args[1].(*models.SaveAlertmanagerConfigurationCmd)) |
||||
}) |
||||
return _c |
||||
} |
||||
|
||||
func (_c *MockAMConfigStore_UpdateAlertmanagerConfiguration_Call) Return(_a0 error) *MockAMConfigStore_UpdateAlertmanagerConfiguration_Call { |
||||
_c.Call.Return(_a0) |
||||
return _c |
||||
} |
||||
|
||||
// NewMockAMConfigStore creates a new instance of MockAMConfigStore. It also registers the testing.TB interface on the mock and a cleanup function to assert the mocks expectations.
|
||||
func NewMockAMConfigStore(t testing.TB) *MockAMConfigStore { |
||||
mock := &MockAMConfigStore{} |
||||
mock.Mock.Test(t) |
||||
|
||||
t.Cleanup(func() { mock.AssertExpectations(t) }) |
||||
|
||||
return mock |
||||
} |
@ -0,0 +1,48 @@ |
||||
package provisioning |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/services/ngalert/models" |
||||
) |
||||
|
||||
type TemplateService struct { |
||||
config AMConfigStore |
||||
prov ProvisioningStore |
||||
xact TransactionManager |
||||
log log.Logger |
||||
} |
||||
|
||||
func NewTemplateService(config AMConfigStore, prov ProvisioningStore, xact TransactionManager, log log.Logger) *TemplateService { |
||||
return &TemplateService{ |
||||
config: config, |
||||
prov: prov, |
||||
xact: xact, |
||||
log: log, |
||||
} |
||||
} |
||||
func (t *TemplateService) GetTemplates(ctx context.Context, orgID int64) (map[string]string, error) { |
||||
q := models.GetLatestAlertmanagerConfigurationQuery{ |
||||
OrgID: orgID, |
||||
} |
||||
err := t.config.GetLatestAlertmanagerConfiguration(ctx, &q) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
if q.Result == nil { |
||||
return nil, fmt.Errorf("no alertmanager configuration present in this org") |
||||
} |
||||
|
||||
cfg, err := DeserializeAlertmanagerConfig([]byte(q.Result.AlertmanagerConfiguration)) |
||||
if err != nil { |
||||
return nil, err |
||||
} |
||||
|
||||
if cfg.TemplateFiles == nil { |
||||
return map[string]string{}, nil |
||||
} |
||||
|
||||
return cfg.TemplateFiles, nil |
||||
} |
@ -0,0 +1,140 @@ |
||||
package provisioning |
||||
|
||||
import ( |
||||
"context" |
||||
"fmt" |
||||
"testing" |
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log" |
||||
"github.com/grafana/grafana/pkg/services/ngalert/models" |
||||
"github.com/grafana/grafana/pkg/setting" |
||||
mock "github.com/stretchr/testify/mock" |
||||
"github.com/stretchr/testify/require" |
||||
) |
||||
|
||||
func TestTemplateService(t *testing.T) { |
||||
t.Run("service returns templates from config file", func(t *testing.T) { |
||||
sut := createTemplateServiceSut() |
||||
sut.config.(*MockAMConfigStore).EXPECT(). |
||||
setupGetConfig(models.AlertConfiguration{ |
||||
AlertmanagerConfiguration: configWithTemplates, |
||||
}) |
||||
|
||||
result, err := sut.GetTemplates(context.Background(), 1) |
||||
|
||||
require.NoError(t, err) |
||||
require.Len(t, result, 1) |
||||
}) |
||||
|
||||
t.Run("service returns empty map when config file contains no templates", func(t *testing.T) { |
||||
sut := createTemplateServiceSut() |
||||
sut.config.(*MockAMConfigStore).EXPECT(). |
||||
setupGetConfig(models.AlertConfiguration{ |
||||
AlertmanagerConfiguration: defaultConfig, |
||||
}) |
||||
|
||||
result, err := sut.GetTemplates(context.Background(), 1) |
||||
|
||||
require.NoError(t, err) |
||||
require.Empty(t, result) |
||||
}) |
||||
|
||||
t.Run("service propagates errors", func(t *testing.T) { |
||||
t.Run("when unable to read config", func(t *testing.T) { |
||||
sut := createTemplateServiceSut() |
||||
sut.config.(*MockAMConfigStore).EXPECT(). |
||||
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). |
||||
Return(fmt.Errorf("failed")) |
||||
|
||||
_, err := sut.GetTemplates(context.Background(), 1) |
||||
|
||||
require.Error(t, err) |
||||
}) |
||||
|
||||
t.Run("when config is invalid", func(t *testing.T) { |
||||
sut := createTemplateServiceSut() |
||||
sut.config.(*MockAMConfigStore).EXPECT(). |
||||
setupGetConfig(models.AlertConfiguration{ |
||||
AlertmanagerConfiguration: brokenConfig, |
||||
}) |
||||
|
||||
_, err := sut.GetTemplates(context.Background(), 1) |
||||
|
||||
require.ErrorContains(t, err, "failed to deserialize") |
||||
}) |
||||
|
||||
t.Run("when no AM config in current org", func(t *testing.T) { |
||||
sut := createTemplateServiceSut() |
||||
sut.config.(*MockAMConfigStore).EXPECT(). |
||||
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). |
||||
Return(nil) |
||||
|
||||
_, err := sut.GetTemplates(context.Background(), 1) |
||||
|
||||
require.ErrorContains(t, err, "no alertmanager configuration") |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
func createTemplateServiceSut() *TemplateService { |
||||
return &TemplateService{ |
||||
config: &MockAMConfigStore{}, |
||||
prov: NewFakeProvisioningStore(), |
||||
xact: newNopTransactionManager(), |
||||
log: log.NewNopLogger(), |
||||
} |
||||
} |
||||
|
||||
func (m *MockAMConfigStore_Expecter) setupGetConfig(ac models.AlertConfiguration) *MockAMConfigStore_Expecter { |
||||
m.GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything). |
||||
Run(func(ctx context.Context, q *models.GetLatestAlertmanagerConfigurationQuery) { |
||||
q.Result = &ac |
||||
}). |
||||
Return(nil) |
||||
return m |
||||
} |
||||
|
||||
var defaultConfig = setting.GetAlertmanagerDefaultConfiguration() |
||||
|
||||
var configWithTemplates = ` |
||||
{ |
||||
"template_files": { |
||||
"a": "template" |
||||
}, |
||||
"alertmanager_config": { |
||||
"route": { |
||||
"receiver": "grafana-default-email" |
||||
}, |
||||
"receivers": [{ |
||||
"name": "grafana-default-email", |
||||
"grafana_managed_receiver_configs": [{ |
||||
"uid": "", |
||||
"name": "email receiver", |
||||
"type": "email", |
||||
"isDefault": true, |
||||
"settings": { |
||||
"addresses": "<example@email.com>" |
||||
} |
||||
}] |
||||
}] |
||||
} |
||||
} |
||||
` |
||||
|
||||
var brokenConfig = ` |
||||
"alertmanager_config": { |
||||
"route": { |
||||
"receiver": "grafana-default-email" |
||||
}, |
||||
"receivers": [{ |
||||
"name": "grafana-default-email", |
||||
"grafana_managed_receiver_configs": [{ |
||||
"uid": "abc", |
||||
"name": "default-email", |
||||
"type": "email", |
||||
"isDefault": true, |
||||
"settings": {} |
||||
}] |
||||
}] |
||||
} |
||||
}` |
Loading…
Reference in new issue