mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Refactor receiver_svc and provisioning config store into legacy_storage package (#90856)
* Add more receivers api tests * Move provisioning config store to new legacy_storage package
This commit is contained in:
parent
0edb0c5c4f
commit
a1f0b599a7
@ -2,7 +2,6 @@ package api
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"errors"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/api/response"
|
"github.com/grafana/grafana/pkg/api/response"
|
||||||
@ -11,7 +10,6 @@ import (
|
|||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotificationSrv struct {
|
type NotificationSrv struct {
|
||||||
@ -50,13 +48,7 @@ func (srv *NotificationSrv) RouteGetReceiver(c *contextmodel.ReqContext, name st
|
|||||||
|
|
||||||
receiver, err := srv.receiverService.GetReceiver(c.Req.Context(), q, c.SignedInUser)
|
receiver, err := srv.receiverService.GetReceiver(c.Req.Context(), q, c.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, notifier.ErrNotFound) {
|
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get receiver", err)
|
||||||
return ErrResp(http.StatusNotFound, err, "receiver not found")
|
|
||||||
}
|
|
||||||
if errors.Is(err, notifier.ErrPermissionDenied) {
|
|
||||||
return ErrResp(http.StatusForbidden, err, "permission denied")
|
|
||||||
}
|
|
||||||
return ErrResp(http.StatusInternalServerError, err, "failed to get receiver")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusOK, receiver)
|
return response.JSON(http.StatusOK, receiver)
|
||||||
@ -73,10 +65,7 @@ func (srv *NotificationSrv) RouteGetReceivers(c *contextmodel.ReqContext) respon
|
|||||||
|
|
||||||
receivers, err := srv.receiverService.GetReceivers(c.Req.Context(), q, c.SignedInUser)
|
receivers, err := srv.receiverService.GetReceivers(c.Req.Context(), q, c.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, notifier.ErrPermissionDenied) {
|
return response.ErrOrFallback(http.StatusInternalServerError, "failed to get receiver groups", err)
|
||||||
return ErrResp(http.StatusForbidden, err, "permission denied")
|
|
||||||
}
|
|
||||||
return ErrResp(http.StatusInternalServerError, err, "failed to get receiver groups")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusOK, receivers)
|
return response.JSON(http.StatusOK, receivers)
|
||||||
|
@ -3,18 +3,24 @@ package api
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||||
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||||
|
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||||
"github.com/grafana/grafana/pkg/services/user"
|
"github.com/grafana/grafana/pkg/services/user"
|
||||||
"github.com/grafana/grafana/pkg/web"
|
"github.com/grafana/grafana/pkg/web"
|
||||||
@ -75,7 +81,7 @@ func TestRouteGetReceiver(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("should pass along not found response", func(t *testing.T) {
|
t.Run("should pass along not found response", func(t *testing.T) {
|
||||||
fakeReceiverSvc.GetReceiverFn = func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
fakeReceiverSvc.GetReceiverFn = func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
return definitions.GettableApiReceiver{}, notifier.ErrNotFound
|
return definitions.GettableApiReceiver{}, notifier.ErrReceiverNotFound.Errorf("")
|
||||||
}
|
}
|
||||||
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
rc := testReqCtx("GET")
|
rc := testReqCtx("GET")
|
||||||
@ -85,7 +91,7 @@ func TestRouteGetReceiver(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("should pass along permission denied response", func(t *testing.T) {
|
t.Run("should pass along permission denied response", func(t *testing.T) {
|
||||||
fakeReceiverSvc.GetReceiverFn = func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
fakeReceiverSvc.GetReceiverFn = func(ctx context.Context, q models.GetReceiverQuery, u identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
return definitions.GettableApiReceiver{}, notifier.ErrPermissionDenied
|
return definitions.GettableApiReceiver{}, ac.ErrAuthorizationBase.Errorf("")
|
||||||
}
|
}
|
||||||
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
rc := testReqCtx("GET")
|
rc := testReqCtx("GET")
|
||||||
@ -155,7 +161,7 @@ func TestRouteGetReceivers(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("should pass along permission denied response", func(t *testing.T) {
|
t.Run("should pass along permission denied response", func(t *testing.T) {
|
||||||
fakeReceiverSvc.GetReceiversFn = func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
fakeReceiverSvc.GetReceiversFn = func(ctx context.Context, q models.GetReceiversQuery, u identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
||||||
return nil, notifier.ErrPermissionDenied
|
return nil, ac.ErrAuthorizationBase.Errorf("")
|
||||||
}
|
}
|
||||||
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
handler := NewNotificationsApi(newNotificationSrv(fakeReceiverSvc))
|
||||||
rc := testReqCtx("GET")
|
rc := testReqCtx("GET")
|
||||||
@ -164,6 +170,246 @@ func TestRouteGetReceivers(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRouteGetReceiversResponses(t *testing.T) {
|
||||||
|
createTestEnv := func(t *testing.T, testConfig string) testEnvironment {
|
||||||
|
env := createTestEnv(t, testConfig)
|
||||||
|
env.ac = &recordingAccessControlFake{
|
||||||
|
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||||
|
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingNotificationsRead) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingReceiversList) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("list receivers", func(t *testing.T) {
|
||||||
|
t.Run("GET returns 200", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testConfig)
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
response := sut.RouteGetReceivers(&rc)
|
||||||
|
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
})
|
||||||
|
t.Run("decrypt true without alert.provisioning.secrets:read permissions returns 403", func(t *testing.T) {
|
||||||
|
recPermCheck := false
|
||||||
|
env := createTestEnv(t, testConfig)
|
||||||
|
env.ac = &recordingAccessControlFake{
|
||||||
|
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||||
|
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingProvisioningReadSecrets) {
|
||||||
|
recPermCheck = true
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "true")
|
||||||
|
|
||||||
|
response := sut.RouteGetReceivers(&rc)
|
||||||
|
|
||||||
|
require.True(t, recPermCheck)
|
||||||
|
require.Equal(t, 403, response.Status())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("json body content is as expected", func(t *testing.T) {
|
||||||
|
expectedDecryptedResponse := `[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"ad95bd8a-49ed-4adc-bf89-1b444fa1aa5b","name":"grafana-default-email","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"},"secureFields":{}}]},{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":true,"settings":{"basicAuthPassword":"testpass","basicAuthUser":"test","url":"http://localhost:9093"},"secureFields":{"basicAuthPassword":true}},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"settings":{"avatar_url":"some avatar","url":"some url","use_discord_username":true},"secureFields":{}}]},{"name":"pagerduty test","grafana_managed_receiver_configs":[{"uid":"b9bf06f8-bde2-4438-9d4a-bba0522dcd4d","name":"pagerduty test","type":"pagerduty","disableResolveMessage":false,"settings":{"client":"some client","integrationKey":"some key","severity":"criticalish"},"secureFields":{"integrationKey":true}}]},{"name":"slack test","grafana_managed_receiver_configs":[{"uid":"cbfd0976-8228-4126-b672-4419f30a9e50","name":"slack test","type":"slack","disableResolveMessage":true,"settings":{"text":"title body test","title":"title test","url":"some secure slack webhook"},"secureFields":{"url":true}}]}]`
|
||||||
|
expectedRedactedResponse := `[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"ad95bd8a-49ed-4adc-bf89-1b444fa1aa5b","name":"grafana-default-email","type":"email","disableResolveMessage":false,"settings":{"addresses":"\u003cexample@email.com\u003e"},"secureFields":{}}]},{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":true,"settings":{"basicAuthPassword":"[REDACTED]","basicAuthUser":"test","url":"http://localhost:9093"},"secureFields":{"basicAuthPassword":true}},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"settings":{"avatar_url":"some avatar","url":"some url","use_discord_username":true},"secureFields":{}}]},{"name":"pagerduty test","grafana_managed_receiver_configs":[{"uid":"b9bf06f8-bde2-4438-9d4a-bba0522dcd4d","name":"pagerduty test","type":"pagerduty","disableResolveMessage":false,"settings":{"client":"some client","integrationKey":"[REDACTED]","severity":"criticalish"},"secureFields":{"integrationKey":true}}]},{"name":"slack test","grafana_managed_receiver_configs":[{"uid":"cbfd0976-8228-4126-b672-4419f30a9e50","name":"slack test","type":"slack","disableResolveMessage":true,"settings":{"text":"title body test","title":"title test","url":"[REDACTED]"},"secureFields":{"url":true}}]}]`
|
||||||
|
expectedListResponse := `[{"name":"grafana-default-email","grafana_managed_receiver_configs":[{"uid":"ad95bd8a-49ed-4adc-bf89-1b444fa1aa5b","name":"grafana-default-email","type":"email","disableResolveMessage":false,"secureFields":null}]},{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":false,"secureFields":null},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"secureFields":null}]},{"name":"pagerduty test","grafana_managed_receiver_configs":[{"uid":"b9bf06f8-bde2-4438-9d4a-bba0522dcd4d","name":"pagerduty test","type":"pagerduty","disableResolveMessage":false,"secureFields":null}]},{"name":"slack test","grafana_managed_receiver_configs":[{"uid":"cbfd0976-8228-4126-b672-4419f30a9e50","name":"slack test","type":"slack","disableResolveMessage":false,"secureFields":null}]}]`
|
||||||
|
t.Run("limit offset", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testContactPointConfig)
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "false")
|
||||||
|
|
||||||
|
var expected []definitions.GettableApiReceiver
|
||||||
|
err := json.Unmarshal([]byte(expectedRedactedResponse), &expected)
|
||||||
|
require.NoError(t, err)
|
||||||
|
type testcase struct {
|
||||||
|
limit int
|
||||||
|
offset int
|
||||||
|
expected []definitions.GettableApiReceiver
|
||||||
|
}
|
||||||
|
testcases := []testcase{
|
||||||
|
{limit: 1, offset: 0, expected: expected[:1]},
|
||||||
|
{limit: 2, offset: 0, expected: expected[:2]},
|
||||||
|
{limit: 4, offset: 0, expected: expected[:4]},
|
||||||
|
{limit: 1, offset: 1, expected: expected[1:2]},
|
||||||
|
{limit: 2, offset: 2, expected: expected[2:4]},
|
||||||
|
{limit: 2, offset: 99, expected: nil},
|
||||||
|
{limit: 0, offset: 0, expected: expected},
|
||||||
|
{limit: 0, offset: 1, expected: expected[1:]},
|
||||||
|
}
|
||||||
|
for _, tc := range testcases {
|
||||||
|
t.Run(fmt.Sprintf("limit %d offset %d", tc.limit, tc.offset), func(t *testing.T) {
|
||||||
|
rc.Context.Req.Form.Set("limit", strconv.Itoa(tc.limit))
|
||||||
|
rc.Context.Req.Form.Set("offset", strconv.Itoa(tc.offset))
|
||||||
|
|
||||||
|
response := sut.RouteGetReceivers(&rc)
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
|
||||||
|
var configs []definitions.GettableApiReceiver
|
||||||
|
err := json.Unmarshal(response.Body(), &configs)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, configs, tc.expected)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
t.Run("decrypt false with read permissions is redacted", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testContactPointConfig)
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "false")
|
||||||
|
|
||||||
|
response := sut.RouteGetReceivers(&rc)
|
||||||
|
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
require.Equal(t, expectedRedactedResponse, string(response.Body())) // TODO: Should this endpoint ever return settings?
|
||||||
|
})
|
||||||
|
t.Run("decrypt false with only list permissions, does not have settings", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testContactPointConfig)
|
||||||
|
env.ac = &recordingAccessControlFake{
|
||||||
|
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||||
|
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingReceiversList) {
|
||||||
|
return true, nil
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "false")
|
||||||
|
|
||||||
|
response := sut.RouteGetReceivers(&rc)
|
||||||
|
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
require.Equal(t, expectedListResponse, string(response.Body()))
|
||||||
|
})
|
||||||
|
t.Run("decrypt true with all permissions, contains decrypted settings", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testContactPointConfig)
|
||||||
|
env.ac = &recordingAccessControlFake{
|
||||||
|
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "true")
|
||||||
|
|
||||||
|
response := sut.RouteGetReceivers(&rc)
|
||||||
|
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
require.Equal(t, expectedDecryptedResponse, string(response.Body())) // TODO: Should this endpoint ever return settings?
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("get receiver", func(t *testing.T) {
|
||||||
|
t.Run("GET returns 200", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testConfig)
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
response := sut.RouteGetReceiver(&rc, "grafana-default-email")
|
||||||
|
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("decrypt true without secrets:read permissions returns 403", func(t *testing.T) {
|
||||||
|
recPermCheck := false
|
||||||
|
env := createTestEnv(t, testConfig)
|
||||||
|
env.ac = &recordingAccessControlFake{
|
||||||
|
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||||
|
if strings.Contains(evaluator.String(), accesscontrol.ActionAlertingReceiversReadSecrets) {
|
||||||
|
recPermCheck = true
|
||||||
|
}
|
||||||
|
return false, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "true")
|
||||||
|
|
||||||
|
response := sut.RouteGetReceiver(&rc, "grafana-default-email")
|
||||||
|
|
||||||
|
require.True(t, recPermCheck)
|
||||||
|
require.Equal(t, 403, response.Status())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("json body content is as expected", func(t *testing.T) {
|
||||||
|
expectedRedactedResponse := `{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":true,"settings":{"basicAuthPassword":"[REDACTED]","basicAuthUser":"test","url":"http://localhost:9093"},"secureFields":{"basicAuthPassword":true}},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"settings":{"avatar_url":"some avatar","url":"some url","use_discord_username":true},"secureFields":{}}]}`
|
||||||
|
expectedDecryptedResponse := `{"name":"multiple integrations","grafana_managed_receiver_configs":[{"uid":"c2090fda-f824-4add-b545-5a4d5c2ef082","name":"multiple integrations","type":"prometheus-alertmanager","disableResolveMessage":true,"settings":{"basicAuthPassword":"testpass","basicAuthUser":"test","url":"http://localhost:9093"},"secureFields":{"basicAuthPassword":true}},{"uid":"c84539ec-f87e-4fc5-9a91-7a687d34bbd1","name":"multiple integrations","type":"discord","disableResolveMessage":false,"settings":{"avatar_url":"some avatar","url":"some url","use_discord_username":true},"secureFields":{}}]}`
|
||||||
|
t.Run("decrypt false", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testContactPointConfig)
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "false")
|
||||||
|
|
||||||
|
response := sut.RouteGetReceiver(&rc, "multiple integrations")
|
||||||
|
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
require.Equal(t, expectedRedactedResponse, string(response.Body()))
|
||||||
|
})
|
||||||
|
t.Run("decrypt true", func(t *testing.T) {
|
||||||
|
env := createTestEnv(t, testContactPointConfig)
|
||||||
|
env.ac = &recordingAccessControlFake{
|
||||||
|
Callback: func(user *user.SignedInUser, evaluator accesscontrol.Evaluator) (bool, error) {
|
||||||
|
return true, nil
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sut := createNotificationSrvSutFromEnv(t, &env)
|
||||||
|
rc := createTestRequestCtx()
|
||||||
|
|
||||||
|
rc.Context.Req.Header.Add("Accept", "application/json")
|
||||||
|
rc.Context.Req.Form.Set("decrypt", "true")
|
||||||
|
|
||||||
|
response := sut.RouteGetReceiver(&rc, "multiple integrations")
|
||||||
|
|
||||||
|
require.Equal(t, 200, response.Status())
|
||||||
|
require.Equal(t, expectedDecryptedResponse, string(response.Body()))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func createNotificationSrvSutFromEnv(t *testing.T, env *testEnvironment) NotificationSrv {
|
||||||
|
t.Helper()
|
||||||
|
|
||||||
|
receiverSvc := notifier.NewReceiverService(
|
||||||
|
env.ac,
|
||||||
|
legacy_storage.NewAlertmanagerConfigStore(env.configs),
|
||||||
|
env.prov,
|
||||||
|
env.secrets,
|
||||||
|
env.xact,
|
||||||
|
env.log,
|
||||||
|
)
|
||||||
|
return NotificationSrv{
|
||||||
|
logger: env.log,
|
||||||
|
receiverService: receiverSvc,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func newNotificationSrv(receiverService ReceiverService) *NotificationSrv {
|
func newNotificationSrv(receiverService ReceiverService) *NotificationSrv {
|
||||||
return &NotificationSrv{
|
return &NotificationSrv{
|
||||||
logger: log.NewNopLogger(),
|
logger: log.NewNopLogger(),
|
||||||
|
@ -138,11 +138,9 @@ func (srv *ProvisioningSrv) RouteGetContactPoints(c *contextmodel.ReqContext) re
|
|||||||
}
|
}
|
||||||
cps, err := srv.contactPointService.GetContactPoints(c.Req.Context(), q, c.SignedInUser)
|
cps, err := srv.contactPointService.GetContactPoints(c.Req.Context(), q, c.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, provisioning.ErrPermissionDenied) {
|
return response.ErrOrFallback(http.StatusInternalServerError, "", err)
|
||||||
return ErrResp(http.StatusForbidden, err, "")
|
|
||||||
}
|
|
||||||
return ErrResp(http.StatusInternalServerError, err, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.JSON(http.StatusOK, cps)
|
return response.JSON(http.StatusOK, cps)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,10 +152,7 @@ func (srv *ProvisioningSrv) RouteGetContactPointsExport(c *contextmodel.ReqConte
|
|||||||
}
|
}
|
||||||
cps, err := srv.contactPointService.GetContactPoints(c.Req.Context(), q, c.SignedInUser)
|
cps, err := srv.contactPointService.GetContactPoints(c.Req.Context(), q, c.SignedInUser)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if errors.Is(err, provisioning.ErrPermissionDenied) {
|
return response.ErrOrFallback(http.StatusInternalServerError, "", err)
|
||||||
return ErrResp(http.StatusForbidden, err, "")
|
|
||||||
}
|
|
||||||
return ErrResp(http.StatusInternalServerError, err, "")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
e, err := AlertingFileExportFromEmbeddedContactPoints(c.SignedInUser.GetOrgID(), cps)
|
e, err := AlertingFileExportFromEmbeddedContactPoints(c.SignedInUser.GetOrgID(), cps)
|
||||||
|
@ -39,6 +39,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
"github.com/grafana/grafana/pkg/services/quota/quotatest"
|
||||||
@ -1753,7 +1754,7 @@ type testEnvironment struct {
|
|||||||
store store.DBstore
|
store store.DBstore
|
||||||
folderService folder.Service
|
folderService folder.Service
|
||||||
dashboardService dashboards.DashboardService
|
dashboardService dashboards.DashboardService
|
||||||
configs provisioning.AMConfigStore
|
configs legacy_storage.AMConfigStore
|
||||||
xact provisioning.TransactionManager
|
xact provisioning.TransactionManager
|
||||||
quotas provisioning.QuotaChecker
|
quotas provisioning.QuotaChecker
|
||||||
prov provisioning.ProvisioningStore
|
prov provisioning.ProvisioningStore
|
||||||
@ -1780,7 +1781,7 @@ func createTestEnv(t *testing.T, testConfig string) testEnvironment {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
log := log.NewNopLogger()
|
log := log.NewNopLogger()
|
||||||
configs := &provisioning.MockAMConfigStore{}
|
configs := &legacy_storage.MockAMConfigStore{}
|
||||||
configs.EXPECT().
|
configs.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: string(raw),
|
AlertmanagerConfiguration: string(raw),
|
||||||
@ -1885,13 +1886,21 @@ func createProvisioningSrvSut(t *testing.T) ProvisioningSrv {
|
|||||||
func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) ProvisioningSrv {
|
func createProvisioningSrvSutFromEnv(t *testing.T, env *testEnvironment) ProvisioningSrv {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
|
|
||||||
receiverSvc := notifier.NewReceiverService(env.ac, env.configs, env.prov, env.secrets, env.xact, env.log)
|
configStore := legacy_storage.NewAlertmanagerConfigStore(env.configs)
|
||||||
|
receiverSvc := notifier.NewReceiverService(
|
||||||
|
env.ac,
|
||||||
|
configStore,
|
||||||
|
env.prov,
|
||||||
|
env.secrets,
|
||||||
|
env.xact,
|
||||||
|
env.log,
|
||||||
|
)
|
||||||
return ProvisioningSrv{
|
return ProvisioningSrv{
|
||||||
log: env.log,
|
log: env.log,
|
||||||
policies: newFakeNotificationPolicyService(),
|
policies: newFakeNotificationPolicyService(),
|
||||||
contactPointService: provisioning.NewContactPointService(env.configs, env.secrets, env.prov, env.xact, receiverSvc, env.log, env.store),
|
contactPointService: provisioning.NewContactPointService(configStore, env.secrets, env.prov, env.xact, receiverSvc, env.log, env.store),
|
||||||
templates: provisioning.NewTemplateService(env.configs, env.prov, env.xact, env.log),
|
templates: provisioning.NewTemplateService(configStore, env.prov, env.xact, env.log),
|
||||||
muteTimings: provisioning.NewMuteTimingService(env.configs, env.prov, env.xact, env.log, env.store),
|
muteTimings: provisioning.NewMuteTimingService(configStore, env.prov, env.xact, env.log, env.store),
|
||||||
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.folderService, env.quotas, env.xact, 60, 10, 100, env.log, &provisioning.NotificationSettingsValidatorProviderFake{}, env.rulesAuthz),
|
alertRules: provisioning.NewAlertRuleService(env.store, env.prov, env.folderService, env.quotas, env.xact, 60, 10, 100, env.log, &provisioning.NotificationSettingsValidatorProviderFake{}, env.rulesAuthz),
|
||||||
folderSvc: env.folderService,
|
folderSvc: env.folderService,
|
||||||
featureManager: env.features,
|
featureManager: env.features,
|
||||||
|
@ -35,6 +35,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
"github.com/grafana/grafana/pkg/services/ngalert/metrics"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/remote"
|
"github.com/grafana/grafana/pkg/services/ngalert/remote"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
"github.com/grafana/grafana/pkg/services/ngalert/schedule"
|
||||||
@ -408,13 +409,21 @@ func (ng *AlertNG) init() error {
|
|||||||
ng.stateManager = stateManager
|
ng.stateManager = stateManager
|
||||||
ng.schedule = scheduler
|
ng.schedule = scheduler
|
||||||
|
|
||||||
receiverService := notifier.NewReceiverService(ng.accesscontrol, ng.store, ng.store, ng.SecretsService, ng.store, ng.Log)
|
configStore := legacy_storage.NewAlertmanagerConfigStore(ng.store)
|
||||||
|
receiverService := notifier.NewReceiverService(
|
||||||
|
ng.accesscontrol,
|
||||||
|
configStore,
|
||||||
|
ng.store,
|
||||||
|
ng.SecretsService,
|
||||||
|
ng.store,
|
||||||
|
ng.Log,
|
||||||
|
)
|
||||||
|
|
||||||
// Provisioning
|
// Provisioning
|
||||||
policyService := provisioning.NewNotificationPolicyService(ng.store, ng.store, ng.store, ng.Cfg.UnifiedAlerting, ng.Log)
|
policyService := provisioning.NewNotificationPolicyService(configStore, ng.store, ng.store, ng.Cfg.UnifiedAlerting, ng.Log)
|
||||||
contactPointService := provisioning.NewContactPointService(ng.store, ng.SecretsService, ng.store, ng.store, receiverService, ng.Log, ng.store)
|
contactPointService := provisioning.NewContactPointService(configStore, ng.SecretsService, ng.store, ng.store, receiverService, ng.Log, ng.store)
|
||||||
templateService := provisioning.NewTemplateService(ng.store, ng.store, ng.store, ng.Log)
|
templateService := provisioning.NewTemplateService(configStore, ng.store, ng.store, ng.Log)
|
||||||
muteTimingService := provisioning.NewMuteTimingService(ng.store, ng.store, ng.store, ng.Log, ng.store)
|
muteTimingService := provisioning.NewMuteTimingService(configStore, ng.store, ng.store, ng.Log, ng.store)
|
||||||
alertRuleService := provisioning.NewAlertRuleService(ng.store, ng.store, ng.folderService, ng.QuotaService, ng.store,
|
alertRuleService := provisioning.NewAlertRuleService(ng.store, ng.store, ng.folderService, ng.QuotaService, ng.store,
|
||||||
int64(ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
int64(ng.Cfg.UnifiedAlerting.DefaultRuleEvaluationInterval.Seconds()),
|
||||||
int64(ng.Cfg.UnifiedAlerting.BaseInterval.Seconds()),
|
int64(ng.Cfg.UnifiedAlerting.BaseInterval.Seconds()),
|
||||||
|
17
pkg/services/ngalert/notifier/legacy_storage/compat.go
Normal file
17
pkg/services/ngalert/notifier/legacy_storage/compat.go
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
package legacy_storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/base64"
|
||||||
|
)
|
||||||
|
|
||||||
|
func NameToUid(name string) string {
|
||||||
|
return base64.RawURLEncoding.EncodeToString([]byte(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
func UidToName(uid string) (string, error) {
|
||||||
|
data, err := base64.RawURLEncoding.DecodeString(uid)
|
||||||
|
if err != nil {
|
||||||
|
return uid, err
|
||||||
|
}
|
||||||
|
return string(data), nil
|
||||||
|
}
|
92
pkg/services/ngalert/notifier/legacy_storage/config.go
Normal file
92
pkg/services/ngalert/notifier/legacy_storage/config.go
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
package legacy_storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
type amConfigStore interface {
|
||||||
|
GetLatestAlertmanagerConfiguration(ctx context.Context, orgID int64) (*models.AlertConfiguration, error)
|
||||||
|
UpdateAlertmanagerConfiguration(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeserializeAlertmanagerConfig(config []byte) (*definitions.PostableUserConfig, error) {
|
||||||
|
result := definitions.PostableUserConfig{}
|
||||||
|
if err := json.Unmarshal(config, &result); err != nil {
|
||||||
|
return nil, makeErrBadAlertmanagerConfiguration(err)
|
||||||
|
}
|
||||||
|
return &result, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func SerializeAlertmanagerConfig(config definitions.PostableUserConfig) ([]byte, error) {
|
||||||
|
return json.Marshal(config)
|
||||||
|
}
|
||||||
|
|
||||||
|
type ConfigRevision struct {
|
||||||
|
Config *definitions.PostableUserConfig
|
||||||
|
ConcurrencyToken string
|
||||||
|
Version string
|
||||||
|
}
|
||||||
|
|
||||||
|
func getLastConfiguration(ctx context.Context, orgID int64, store amConfigStore) (*ConfigRevision, error) {
|
||||||
|
alertManagerConfig, err := store.GetLatestAlertmanagerConfiguration(ctx, orgID)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if alertManagerConfig == nil {
|
||||||
|
return nil, ErrNoAlertmanagerConfiguration.Errorf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
concurrencyToken := alertManagerConfig.ConfigurationHash
|
||||||
|
cfg, err := DeserializeAlertmanagerConfig([]byte(alertManagerConfig.AlertmanagerConfiguration))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &ConfigRevision{
|
||||||
|
Config: cfg,
|
||||||
|
ConcurrencyToken: concurrencyToken,
|
||||||
|
Version: alertManagerConfig.ConfigurationVersion,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type alertmanagerConfigStoreImpl struct {
|
||||||
|
store amConfigStore
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewAlertmanagerConfigStore(store amConfigStore) *alertmanagerConfigStoreImpl {
|
||||||
|
return &alertmanagerConfigStoreImpl{store: store}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a alertmanagerConfigStoreImpl) Get(ctx context.Context, orgID int64) (*ConfigRevision, error) {
|
||||||
|
return getLastConfiguration(ctx, orgID, a.store)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a alertmanagerConfigStoreImpl) Save(ctx context.Context, revision *ConfigRevision, orgID int64) error {
|
||||||
|
serialized, err := SerializeAlertmanagerConfig(*revision.Config)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cmd := models.SaveAlertmanagerConfigurationCmd{
|
||||||
|
AlertmanagerConfiguration: string(serialized),
|
||||||
|
ConfigurationVersion: revision.Version,
|
||||||
|
FetchedConfigurationHash: revision.ConcurrencyToken,
|
||||||
|
Default: false,
|
||||||
|
OrgID: orgID,
|
||||||
|
}
|
||||||
|
return a.PersistConfig(ctx, &cmd)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PersistConfig validates to config before eventually persisting it if no error occurs
|
||||||
|
func (a alertmanagerConfigStoreImpl) PersistConfig(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error {
|
||||||
|
cfg := &definitions.PostableUserConfig{}
|
||||||
|
if err := json.Unmarshal([]byte(cmd.AlertmanagerConfiguration), cfg); err != nil {
|
||||||
|
return fmt.Errorf("change would result in an invalid configuration state: %w", err)
|
||||||
|
}
|
||||||
|
return a.store.UpdateAlertmanagerConfiguration(ctx, cmd)
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package provisioning
|
package legacy_storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
@ -13,8 +13,11 @@ import (
|
|||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var defaultConfig = setting.GetAlertmanagerDefaultConfiguration()
|
||||||
|
|
||||||
func TestAlertmanagerConfigStoreGet(t *testing.T) {
|
func TestAlertmanagerConfigStoreGet(t *testing.T) {
|
||||||
orgID := int64(1)
|
orgID := int64(1)
|
||||||
|
|
||||||
@ -40,9 +43,9 @@ func TestAlertmanagerConfigStoreGet(t *testing.T) {
|
|||||||
revision, err := store.Get(context.Background(), orgID)
|
revision, err := store.Get(context.Background(), orgID)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.Equal(t, expected.ConfigurationVersion, revision.version)
|
require.Equal(t, expected.ConfigurationVersion, revision.Version)
|
||||||
require.Equal(t, expected.ConfigurationHash, revision.concurrencyToken)
|
require.Equal(t, expected.ConfigurationHash, revision.ConcurrencyToken)
|
||||||
require.Equal(t, expectedCfg, *revision.cfg)
|
require.Equal(t, expectedCfg, *revision.Config)
|
||||||
|
|
||||||
storeMock.AssertCalled(t, "GetLatestAlertmanagerConfiguration", mock.Anything, orgID)
|
storeMock.AssertCalled(t, "GetLatestAlertmanagerConfiguration", mock.Anything, orgID)
|
||||||
})
|
})
|
||||||
@ -85,13 +88,13 @@ func TestAlertmanagerConfigStoreSave(t *testing.T) {
|
|||||||
|
|
||||||
cfg := definitions.PostableUserConfig{}
|
cfg := definitions.PostableUserConfig{}
|
||||||
require.NoError(t, json.Unmarshal([]byte(defaultConfig), &cfg))
|
require.NoError(t, json.Unmarshal([]byte(defaultConfig), &cfg))
|
||||||
expectedCfg, err := serializeAlertmanagerConfig(cfg)
|
expectedCfg, err := SerializeAlertmanagerConfig(cfg)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
revision := cfgRevision{
|
revision := ConfigRevision{
|
||||||
cfg: &cfg,
|
Config: &cfg,
|
||||||
concurrencyToken: "config-hash-123",
|
ConcurrencyToken: "config-hash-123",
|
||||||
version: "123",
|
Version: "123",
|
||||||
}
|
}
|
||||||
|
|
||||||
t.Run("should save the config to store", func(t *testing.T) {
|
t.Run("should save the config to store", func(t *testing.T) {
|
||||||
@ -101,9 +104,9 @@ func TestAlertmanagerConfigStoreSave(t *testing.T) {
|
|||||||
storeMock.EXPECT().UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error {
|
storeMock.EXPECT().UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).RunAndReturn(func(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error {
|
||||||
assert.Equal(t, string(expectedCfg), cmd.AlertmanagerConfiguration)
|
assert.Equal(t, string(expectedCfg), cmd.AlertmanagerConfiguration)
|
||||||
assert.Equal(t, orgID, cmd.OrgID)
|
assert.Equal(t, orgID, cmd.OrgID)
|
||||||
assert.Equal(t, revision.version, cmd.ConfigurationVersion)
|
assert.Equal(t, revision.Version, cmd.ConfigurationVersion)
|
||||||
assert.Equal(t, false, cmd.Default)
|
assert.Equal(t, false, cmd.Default)
|
||||||
assert.Equal(t, revision.concurrencyToken, cmd.FetchedConfigurationHash)
|
assert.Equal(t, revision.ConcurrencyToken, cmd.FetchedConfigurationHash)
|
||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
18
pkg/services/ngalert/notifier/legacy_storage/errors.go
Normal file
18
pkg/services/ngalert/notifier/legacy_storage/errors.go
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
package legacy_storage
|
||||||
|
|
||||||
|
import "github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||||
|
|
||||||
|
var (
|
||||||
|
ErrNoAlertmanagerConfiguration = errutil.Internal("alerting.notification.configMissing", errutil.WithPublicMessage("No alertmanager configuration present in this organization"))
|
||||||
|
ErrBadAlertmanagerConfiguration = errutil.Internal("alerting.notification.configCorrupted").MustTemplate("Failed to unmarshal the Alertmanager configuration", errutil.WithPublic("Current Alertmanager configuration in the storage is corrupted. Reset the configuration or rollback to a recent valid one."))
|
||||||
|
)
|
||||||
|
|
||||||
|
func makeErrBadAlertmanagerConfiguration(err error) error {
|
||||||
|
data := errutil.TemplateData{
|
||||||
|
Public: map[string]interface{}{
|
||||||
|
"Error": err.Error(),
|
||||||
|
},
|
||||||
|
Error: err,
|
||||||
|
}
|
||||||
|
return ErrBadAlertmanagerConfiguration.Build(data)
|
||||||
|
}
|
15
pkg/services/ngalert/notifier/legacy_storage/persist.go
Normal file
15
pkg/services/ngalert/notifier/legacy_storage/persist.go
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
package legacy_storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
// AMStore is a store of Alertmanager configurations.
|
||||||
|
//
|
||||||
|
//go:generate mockery --name AMConfigStore --structname MockAMConfigStore --inpackage --filename persist_mock.go --with-expecter
|
||||||
|
type AMConfigStore interface {
|
||||||
|
GetLatestAlertmanagerConfiguration(ctx context.Context, orgID int64) (*models.AlertConfiguration, error)
|
||||||
|
UpdateAlertmanagerConfiguration(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
// Code generated by mockery v2.34.2. DO NOT EDIT.
|
// Code generated by mockery v2.34.2. DO NOT EDIT.
|
||||||
|
|
||||||
package provisioning
|
package legacy_storage
|
||||||
|
|
||||||
import (
|
import (
|
||||||
context "context"
|
context "context"
|
53
pkg/services/ngalert/notifier/legacy_storage/receivers.go
Normal file
53
pkg/services/ngalert/notifier/legacy_storage/receivers.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package legacy_storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (rev *ConfigRevision) DeleteReceiver(uid string) {
|
||||||
|
// Remove the receiver from the configuration.
|
||||||
|
rev.Config.AlertmanagerConfig.Receivers = slices.DeleteFunc(rev.Config.AlertmanagerConfig.Receivers, func(r *definitions.PostableApiReceiver) bool {
|
||||||
|
return NameToUid(r.GetName()) == uid
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rev *ConfigRevision) ReceiverNameUsedByRoutes(name string) bool {
|
||||||
|
return isReceiverInUse(name, []*definitions.Route{rev.Config.AlertmanagerConfig.Route})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rev *ConfigRevision) GetReceiver(uid string) *definitions.PostableApiReceiver {
|
||||||
|
for _, r := range rev.Config.AlertmanagerConfig.Receivers {
|
||||||
|
if NameToUid(r.GetName()) == uid {
|
||||||
|
return r
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rev *ConfigRevision) GetReceivers(uids []string) []*definitions.PostableApiReceiver {
|
||||||
|
receivers := make([]*definitions.PostableApiReceiver, 0, len(uids))
|
||||||
|
for _, r := range rev.Config.AlertmanagerConfig.Receivers {
|
||||||
|
if len(uids) == 0 || slices.Contains(uids, NameToUid(r.GetName())) {
|
||||||
|
receivers = append(receivers, r)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return receivers
|
||||||
|
}
|
||||||
|
|
||||||
|
// isReceiverInUse checks if a receiver is used in a route or any of its sub-routes.
|
||||||
|
func isReceiverInUse(name string, routes []*definitions.Route) bool {
|
||||||
|
if len(routes) == 0 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, route := range routes {
|
||||||
|
if route.Receiver == name {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
if isReceiverInUse(name, route.Routes) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
@ -0,0 +1,40 @@
|
|||||||
|
package legacy_storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestReceiverInUse(t *testing.T) {
|
||||||
|
result := isReceiverInUse("test", []*definitions.Route{
|
||||||
|
{
|
||||||
|
Receiver: "not-test",
|
||||||
|
Routes: []*definitions.Route{
|
||||||
|
{
|
||||||
|
Receiver: "not-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Receiver: "test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.True(t, result)
|
||||||
|
result = isReceiverInUse("test", []*definitions.Route{
|
||||||
|
{
|
||||||
|
Receiver: "not-test",
|
||||||
|
Routes: []*definitions.Route{
|
||||||
|
{
|
||||||
|
Receiver: "not-test",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Receiver: "not-test",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
require.False(t, result)
|
||||||
|
}
|
61
pkg/services/ngalert/notifier/legacy_storage/testing.go
Normal file
61
pkg/services/ngalert/notifier/legacy_storage/testing.go
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
package legacy_storage
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/mock"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (m *MockAMConfigStore_Expecter) GetsConfig(ac models.AlertConfiguration) *MockAMConfigStore_Expecter {
|
||||||
|
m.GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(&ac, nil)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockAMConfigStore_Expecter) SaveSucceeds() *MockAMConfigStore_Expecter {
|
||||||
|
m.UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(nil)
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
func (m *MockAMConfigStore_Expecter) SaveSucceedsIntercept(intercepted *models.SaveAlertmanagerConfigurationCmd) *MockAMConfigStore_Expecter {
|
||||||
|
m.UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
|
Return(nil).
|
||||||
|
Run(func(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) {
|
||||||
|
*intercepted = *cmd
|
||||||
|
})
|
||||||
|
return m
|
||||||
|
}
|
||||||
|
|
||||||
|
type methodCall struct {
|
||||||
|
Method string
|
||||||
|
Args []interface{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type AlertmanagerConfigStoreFake struct {
|
||||||
|
Calls []methodCall
|
||||||
|
GetFn func(ctx context.Context, orgID int64) (*ConfigRevision, error)
|
||||||
|
SaveFn func(ctx context.Context, revision *ConfigRevision) error
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AlertmanagerConfigStoreFake) Get(ctx context.Context, orgID int64) (*ConfigRevision, error) {
|
||||||
|
a.Calls = append(a.Calls, methodCall{
|
||||||
|
Method: "Get",
|
||||||
|
Args: []interface{}{ctx, orgID},
|
||||||
|
})
|
||||||
|
if a.GetFn != nil {
|
||||||
|
return a.GetFn(ctx, orgID)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AlertmanagerConfigStoreFake) Save(ctx context.Context, revision *ConfigRevision, orgID int64) error {
|
||||||
|
a.Calls = append(a.Calls, methodCall{
|
||||||
|
Method: "Save",
|
||||||
|
Args: []interface{}{ctx, revision, orgID},
|
||||||
|
})
|
||||||
|
if a.SaveFn != nil {
|
||||||
|
return a.SaveFn(ctx, revision)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
@ -3,52 +3,45 @@ package notifier
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/json"
|
|
||||||
"errors"
|
"github.com/grafana/alerting/definition"
|
||||||
"fmt"
|
|
||||||
"hash/fnv"
|
|
||||||
"slices"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||||
|
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
// ErrPermissionDenied is returned when the user does not have permission to perform the requested action.
|
ErrReceiverNotFound = errutil.NotFound("alerting.notifications.receiver.notFound")
|
||||||
ErrPermissionDenied = errors.New("permission denied") // TODO: convert to errutil
|
ErrReceiverInUse = errutil.Conflict("alerting.notifications.receiver.used").MustTemplate("Receiver is used by notification policies or alert rules")
|
||||||
// ErrNotFound is returned when the requested resource does not exist.
|
|
||||||
ErrNotFound = errors.New("not found") // TODO: convert to errutil
|
|
||||||
)
|
|
||||||
|
|
||||||
var (
|
|
||||||
ErrReceiverInUse = errutil.Conflict("alerting.notifications.receiver.used", errutil.WithPublicMessage("Receiver is used by one or many notification policies"))
|
|
||||||
ErrVersionConflict = errutil.Conflict("alerting.notifications.receiver.conflict")
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// ReceiverService is the service for managing alertmanager receivers.
|
// ReceiverService is the service for managing alertmanager receivers.
|
||||||
type ReceiverService struct {
|
type ReceiverService struct {
|
||||||
ac accesscontrol.AccessControl
|
ac accesscontrol.AccessControl
|
||||||
provisioningStore provisoningStore
|
provisioningStore provisoningStore
|
||||||
cfgStore configStore
|
cfgStore alertmanagerConfigStore
|
||||||
encryptionService secrets.Service
|
encryptionService secrets.Service
|
||||||
xact transactionManager
|
xact transactionManager
|
||||||
log log.Logger
|
log log.Logger
|
||||||
validator validation.ProvenanceStatusTransitionValidator
|
validator validation.ProvenanceStatusTransitionValidator
|
||||||
}
|
}
|
||||||
|
|
||||||
type configStore interface {
|
type alertmanagerConfigStore interface {
|
||||||
GetLatestAlertmanagerConfiguration(ctx context.Context, orgID int64) (*models.AlertConfiguration, error)
|
Get(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error)
|
||||||
UpdateAlertmanagerConfiguration(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error
|
Save(ctx context.Context, revision *legacy_storage.ConfigRevision, orgID int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
type provisoningStore interface {
|
type provisoningStore interface {
|
||||||
GetProvenances(ctx context.Context, org int64, resourceType string) (map[string]models.Provenance, error)
|
GetProvenances(ctx context.Context, org int64, resourceType string) (map[string]models.Provenance, error)
|
||||||
|
SetProvenance(ctx context.Context, o models.Provisionable, org int64, p models.Provenance) error
|
||||||
DeleteProvenance(ctx context.Context, o models.Provisionable, org int64) error
|
DeleteProvenance(ctx context.Context, o models.Provisionable, org int64) error
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -58,7 +51,7 @@ type transactionManager interface {
|
|||||||
|
|
||||||
func NewReceiverService(
|
func NewReceiverService(
|
||||||
ac accesscontrol.AccessControl,
|
ac accesscontrol.AccessControl,
|
||||||
cfgStore configStore,
|
cfgStore alertmanagerConfigStore,
|
||||||
provisioningStore provisoningStore,
|
provisioningStore provisoningStore,
|
||||||
encryptionService secrets.Service,
|
encryptionService secrets.Service,
|
||||||
xact transactionManager,
|
xact transactionManager,
|
||||||
@ -82,7 +75,7 @@ func (rs *ReceiverService) shouldDecrypt(ctx context.Context, user identity.Requ
|
|||||||
}
|
}
|
||||||
|
|
||||||
if reqDecrypt && !decryptAccess {
|
if reqDecrypt && !decryptAccess {
|
||||||
return false, ErrPermissionDenied
|
return false, ac.NewAuthorizationErrorWithPermissions("read any decrypted receiver", nil) // TODO: Replace with authz service.
|
||||||
}
|
}
|
||||||
|
|
||||||
return decryptAccess && reqDecrypt, nil
|
return decryptAccess && reqDecrypt, nil
|
||||||
@ -117,60 +110,51 @@ func (rs *ReceiverService) hasList(ctx context.Context, user identity.Requester)
|
|||||||
// The receiver's secure settings are decrypted if requested and the user has access to do so.
|
// The receiver's secure settings are decrypted if requested and the user has access to do so.
|
||||||
func (rs *ReceiverService) GetReceiver(ctx context.Context, q models.GetReceiverQuery, user identity.Requester) (definitions.GettableApiReceiver, error) {
|
func (rs *ReceiverService) GetReceiver(ctx context.Context, q models.GetReceiverQuery, user identity.Requester) (definitions.GettableApiReceiver, error) {
|
||||||
if q.Decrypt && user == nil {
|
if q.Decrypt && user == nil {
|
||||||
return definitions.GettableApiReceiver{}, ErrPermissionDenied
|
return definitions.GettableApiReceiver{}, ac.NewAuthorizationErrorWithPermissions("read any decrypted receiver", nil) // TODO: Replace with authz service.
|
||||||
}
|
}
|
||||||
|
|
||||||
baseCfg, err := rs.cfgStore.GetLatestAlertmanagerConfiguration(ctx, q.OrgID)
|
revision, err := rs.cfgStore.Get(ctx, q.OrgID)
|
||||||
|
if err != nil {
|
||||||
|
return definitions.GettableApiReceiver{}, err
|
||||||
|
}
|
||||||
|
postable := revision.GetReceiver(legacy_storage.NameToUid(q.Name))
|
||||||
|
if postable == nil {
|
||||||
|
return definitions.GettableApiReceiver{}, ErrReceiverNotFound.Errorf("")
|
||||||
|
}
|
||||||
|
|
||||||
|
decrypt, err := rs.shouldDecrypt(ctx, user, q.Decrypt)
|
||||||
|
if err != nil {
|
||||||
|
return definitions.GettableApiReceiver{}, err
|
||||||
|
}
|
||||||
|
decryptFn := rs.decryptOrRedact(ctx, decrypt, q.Name, "")
|
||||||
|
|
||||||
|
storedProvenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, (&definitions.EmbeddedContactPoint{}).ResourceType())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return definitions.GettableApiReceiver{}, err
|
return definitions.GettableApiReceiver{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg := definitions.PostableUserConfig{}
|
return PostableToGettableApiReceiver(postable, storedProvenances, decryptFn, false)
|
||||||
err = json.Unmarshal([]byte(baseCfg.AlertmanagerConfiguration), &cfg)
|
|
||||||
if err != nil {
|
|
||||||
return definitions.GettableApiReceiver{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
provenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, (&definitions.EmbeddedContactPoint{}).ResourceType())
|
|
||||||
if err != nil {
|
|
||||||
return definitions.GettableApiReceiver{}, err
|
|
||||||
}
|
|
||||||
|
|
||||||
receivers := cfg.AlertmanagerConfig.Receivers
|
|
||||||
for _, r := range receivers {
|
|
||||||
if r.Name == q.Name {
|
|
||||||
decrypt, err := rs.shouldDecrypt(ctx, user, q.Decrypt)
|
|
||||||
if err != nil {
|
|
||||||
return definitions.GettableApiReceiver{}, err
|
|
||||||
}
|
|
||||||
decryptFn := rs.decryptOrRedact(ctx, decrypt, q.Name, "")
|
|
||||||
|
|
||||||
return PostableToGettableApiReceiver(r, provenances, decryptFn, false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return definitions.GettableApiReceiver{}, ErrNotFound
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetReceivers returns a list of receivers a user has access to.
|
// GetReceivers returns a list of receivers a user has access to.
|
||||||
// Receivers can be filtered by name, and secure settings are decrypted if requested and the user has access to do so.
|
// Receivers can be filtered by name, and secure settings are decrypted if requested and the user has access to do so.
|
||||||
func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceiversQuery, user identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceiversQuery, user identity.Requester) ([]definitions.GettableApiReceiver, error) {
|
||||||
if q.Decrypt && user == nil {
|
if q.Decrypt && user == nil {
|
||||||
return nil, ErrPermissionDenied
|
return nil, ac.NewAuthorizationErrorWithPermissions("read any decrypted receiver", nil) // TODO: Replace with authz service.
|
||||||
}
|
}
|
||||||
|
|
||||||
baseCfg, err := rs.cfgStore.GetLatestAlertmanagerConfiguration(ctx, q.OrgID)
|
uids := make([]string, 0, len(q.Names))
|
||||||
|
for _, name := range q.Names {
|
||||||
|
uids = append(uids, legacy_storage.NameToUid(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
revision, err := rs.cfgStore.Get(ctx, q.OrgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
postables := revision.GetReceivers(uids)
|
||||||
|
|
||||||
cfg := definitions.PostableUserConfig{}
|
storedProvenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, (&definitions.EmbeddedContactPoint{}).ResourceType())
|
||||||
err = json.Unmarshal([]byte(baseCfg.AlertmanagerConfiguration), &cfg)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
provenances, err := rs.provisioningStore.GetProvenances(ctx, q.OrgID, (&definitions.EmbeddedContactPoint{}).ResourceType())
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -188,15 +172,12 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
|
|||||||
// User doesn't have any permissions on the receivers.
|
// User doesn't have any permissions on the receivers.
|
||||||
// This is mostly a safeguard as it should not be possible with current API endpoints + middleware authentication.
|
// This is mostly a safeguard as it should not be possible with current API endpoints + middleware authentication.
|
||||||
if !listAccess && !readRedactedAccess {
|
if !listAccess && !readRedactedAccess {
|
||||||
return nil, ErrPermissionDenied
|
return nil, ac.NewAuthorizationErrorWithPermissions("read any receiver", nil) // TODO: Replace with authz service.
|
||||||
}
|
}
|
||||||
|
|
||||||
var output []definitions.GettableApiReceiver
|
var output []definitions.GettableApiReceiver
|
||||||
for i := q.Offset; i < len(cfg.AlertmanagerConfig.Receivers); i++ {
|
for i := q.Offset; i < len(postables); i++ {
|
||||||
r := cfg.AlertmanagerConfig.Receivers[i]
|
r := postables[i]
|
||||||
if len(q.Names) > 0 && !slices.Contains(q.Names, r.Name) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
decrypt, err := rs.shouldDecrypt(ctx, user, q.Decrypt)
|
decrypt, err := rs.shouldDecrypt(ctx, user, q.Decrypt)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -210,7 +191,7 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
|
|||||||
// - Doesn't have ReadRedacted (or ReadDecrypted permission since it's a subset).
|
// - Doesn't have ReadRedacted (or ReadDecrypted permission since it's a subset).
|
||||||
listOnly := !readRedactedAccess
|
listOnly := !readRedactedAccess
|
||||||
|
|
||||||
res, err := PostableToGettableApiReceiver(r, provenances, decryptFn, listOnly)
|
res, err := PostableToGettableApiReceiver(r, storedProvenances, decryptFn, listOnly)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@ -229,25 +210,18 @@ func (rs *ReceiverService) GetReceivers(ctx context.Context, q models.GetReceive
|
|||||||
// UID field currently does not exist, we assume the uid is a particular hashed value of the receiver name.
|
// UID field currently does not exist, we assume the uid is a particular hashed value of the receiver name.
|
||||||
func (rs *ReceiverService) DeleteReceiver(ctx context.Context, uid string, orgID int64, callerProvenance definitions.Provenance, version string) error {
|
func (rs *ReceiverService) DeleteReceiver(ctx context.Context, uid string, orgID int64, callerProvenance definitions.Provenance, version string) error {
|
||||||
//TODO: Check delete permissions.
|
//TODO: Check delete permissions.
|
||||||
baseCfg, err := rs.cfgStore.GetLatestAlertmanagerConfiguration(ctx, orgID)
|
revision, err := rs.cfgStore.Get(ctx, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
postable := revision.GetReceiver(uid)
|
||||||
cfg := definitions.PostableUserConfig{}
|
if postable == nil {
|
||||||
err = json.Unmarshal([]byte(baseCfg.AlertmanagerConfiguration), &cfg)
|
return ErrReceiverNotFound.Errorf("")
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
idx, recv := getReceiverByUID(cfg, uid)
|
|
||||||
if recv == nil {
|
|
||||||
return ErrNotFound // TODO: nil?
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Implement + check optimistic concurrency.
|
// TODO: Implement + check optimistic concurrency.
|
||||||
|
|
||||||
storedProvenance, err := rs.getContactPointProvenance(ctx, recv, orgID)
|
storedProvenance, err := rs.getContactPointProvenance(ctx, postable, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -256,39 +230,24 @@ func (rs *ReceiverService) DeleteReceiver(ctx context.Context, uid string, orgID
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if isReceiverInUse(recv.Name, []*definitions.Route{cfg.AlertmanagerConfig.Route}) {
|
usedByRoutes := revision.ReceiverNameUsedByRoutes(postable.GetName())
|
||||||
return ErrReceiverInUse.Errorf("")
|
usedByRules, err := rs.UsedByRules(ctx, orgID, uid)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove the receiver from the configuration.
|
if usedByRoutes || len(usedByRules) > 0 {
|
||||||
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers[:idx], cfg.AlertmanagerConfig.Receivers[idx+1:]...)
|
return makeReceiverInUseErr(usedByRoutes, usedByRules)
|
||||||
|
}
|
||||||
|
|
||||||
|
revision.DeleteReceiver(uid)
|
||||||
|
|
||||||
return rs.xact.InTransaction(ctx, func(ctx context.Context) error {
|
return rs.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||||
serialized, err := json.Marshal(cfg)
|
err = rs.cfgStore.Save(ctx, revision, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
cmd := models.SaveAlertmanagerConfigurationCmd{
|
return rs.deleteProvenances(ctx, orgID, postable.GrafanaManagedReceivers)
|
||||||
AlertmanagerConfiguration: string(serialized),
|
|
||||||
ConfigurationVersion: baseCfg.ConfigurationVersion,
|
|
||||||
FetchedConfigurationHash: baseCfg.ConfigurationHash,
|
|
||||||
Default: false,
|
|
||||||
OrgID: orgID,
|
|
||||||
}
|
|
||||||
|
|
||||||
err = rs.cfgStore.UpdateAlertmanagerConfiguration(ctx, &cmd)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove provenance for all integrations in the receiver.
|
|
||||||
for _, integration := range recv.GrafanaManagedReceivers {
|
|
||||||
target := definitions.EmbeddedContactPoint{UID: integration.UID}
|
|
||||||
if err := rs.provisioningStore.DeleteProvenance(ctx, &target, orgID); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -302,6 +261,22 @@ func (rs *ReceiverService) UpdateReceiver(ctx context.Context, r definitions.Get
|
|||||||
panic("not implemented")
|
panic("not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rs *ReceiverService) UsedByRules(ctx context.Context, orgID int64, uid string) ([]models.AlertRuleKey, error) {
|
||||||
|
//TODO: Implement
|
||||||
|
return []models.AlertRuleKey{}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (rs *ReceiverService) deleteProvenances(ctx context.Context, orgID int64, integrations []*definition.PostableGrafanaReceiver) error {
|
||||||
|
// Delete provenance for all integrations.
|
||||||
|
for _, integration := range integrations {
|
||||||
|
target := definitions.EmbeddedContactPoint{UID: integration.UID}
|
||||||
|
if err := rs.provisioningStore.DeleteProvenance(ctx, &target, orgID); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
func (rs *ReceiverService) decryptOrRedact(ctx context.Context, decrypt bool, name, fallback string) func(value string) string {
|
func (rs *ReceiverService) decryptOrRedact(ctx context.Context, decrypt bool, name, fallback string) func(value string) string {
|
||||||
return func(value string) string {
|
return func(value string) string {
|
||||||
if !decrypt {
|
if !decrypt {
|
||||||
@ -345,37 +320,21 @@ func (rs *ReceiverService) getContactPointProvenance(ctx context.Context, r *def
|
|||||||
return models.ProvenanceNone, nil
|
return models.ProvenanceNone, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// getReceiverByUID returns the index and receiver with the given UID.
|
func makeReceiverInUseErr(usedByRoutes bool, rules []models.AlertRuleKey) error {
|
||||||
func getReceiverByUID(cfg definitions.PostableUserConfig, uid string) (int, *definitions.PostableApiReceiver) {
|
uids := make([]string, 0, len(rules))
|
||||||
for i, r := range cfg.AlertmanagerConfig.Receivers {
|
for _, key := range rules {
|
||||||
if getUID(r) == uid {
|
uids = append(uids, key.UID)
|
||||||
return i, r
|
}
|
||||||
}
|
data := make(map[string]any, 2)
|
||||||
|
if len(uids) > 0 {
|
||||||
|
data["UsedByRules"] = uids
|
||||||
|
}
|
||||||
|
if usedByRoutes {
|
||||||
|
data["UsedByRoutes"] = true
|
||||||
}
|
}
|
||||||
return 0, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
// getUID returns the UID of a PostableApiReceiver.
|
return ErrReceiverInUse.Build(errutil.TemplateData{
|
||||||
// Currently, the UID is a hash of the receiver name.
|
Public: data,
|
||||||
func getUID(t *definitions.PostableApiReceiver) string { // TODO replace to stable UID when we switch to normal storage
|
Error: nil,
|
||||||
sum := fnv.New64()
|
})
|
||||||
_, _ = sum.Write([]byte(t.Name))
|
|
||||||
return fmt.Sprintf("%016x", sum.Sum64())
|
|
||||||
}
|
|
||||||
|
|
||||||
// TODO: Check if the contact point is used directly in an alert rule.
|
|
||||||
// isReceiverInUse checks if a receiver is used in a route or any of its sub-routes.
|
|
||||||
func isReceiverInUse(name string, routes []*definitions.Route) bool {
|
|
||||||
if len(routes) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, route := range routes {
|
|
||||||
if route.Receiver == name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if isReceiverInUse(name, route.Routes) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import (
|
|||||||
|
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||||
"github.com/grafana/grafana/pkg/components/simplejson"
|
"github.com/grafana/grafana/pkg/components/simplejson"
|
||||||
"github.com/grafana/grafana/pkg/infra/db"
|
"github.com/grafana/grafana/pkg/infra/db"
|
||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
@ -17,7 +18,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||||
@ -49,7 +50,7 @@ func TestReceiverService_GetReceiver(t *testing.T) {
|
|||||||
sut := createReceiverServiceSut(t, secretsService)
|
sut := createReceiverServiceSut(t, secretsService)
|
||||||
|
|
||||||
_, err := sut.GetReceiver(context.Background(), singleQ(1, "nonexistent"), redactedUser)
|
_, err := sut.GetReceiver(context.Background(), singleQ(1, "nonexistent"), redactedUser)
|
||||||
require.ErrorIs(t, err, ErrNotFound)
|
require.ErrorIs(t, err, ErrReceiverNotFound.Errorf(""))
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,32 +110,32 @@ func TestReceiverService_DecryptRedact(t *testing.T) {
|
|||||||
for _, tc := range []struct {
|
for _, tc := range []struct {
|
||||||
name string
|
name string
|
||||||
decrypt bool
|
decrypt bool
|
||||||
user *user.SignedInUser
|
user identity.Requester
|
||||||
err error
|
err string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "service redacts receivers by default",
|
name: "service redacts receivers by default",
|
||||||
decrypt: false,
|
decrypt: false,
|
||||||
user: readUser,
|
user: readUser,
|
||||||
err: nil,
|
err: "",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "service returns error when trying to decrypt without permission",
|
name: "service returns error when trying to decrypt without permission",
|
||||||
decrypt: true,
|
decrypt: true,
|
||||||
user: readUser,
|
user: readUser,
|
||||||
err: ErrPermissionDenied,
|
err: "[alerting.unauthorized] user is not authorized to read any decrypted receiver",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "service returns error if user is nil and decrypt is true",
|
name: "service returns error if user is nil and decrypt is true",
|
||||||
decrypt: true,
|
decrypt: true,
|
||||||
user: nil,
|
user: nil,
|
||||||
err: ErrPermissionDenied,
|
err: "[alerting.unauthorized] user is not authorized to read any decrypted receiver",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "service decrypts receivers with permission",
|
name: "service decrypts receivers with permission",
|
||||||
decrypt: true,
|
decrypt: true,
|
||||||
user: secretUser,
|
user: secretUser,
|
||||||
err: nil,
|
err: "",
|
||||||
},
|
},
|
||||||
} {
|
} {
|
||||||
for _, method := range getMethods {
|
for _, method := range getMethods {
|
||||||
@ -152,14 +153,18 @@ func TestReceiverService_DecryptRedact(t *testing.T) {
|
|||||||
q.Decrypt = tc.decrypt
|
q.Decrypt = tc.decrypt
|
||||||
var multiRes []definitions.GettableApiReceiver
|
var multiRes []definitions.GettableApiReceiver
|
||||||
multiRes, err = sut.GetReceivers(context.Background(), q, tc.user)
|
multiRes, err = sut.GetReceivers(context.Background(), q, tc.user)
|
||||||
if tc.err == nil {
|
if tc.err == "" {
|
||||||
require.Len(t, multiRes, 1)
|
require.Len(t, multiRes, 1)
|
||||||
res = multiRes[0]
|
res = multiRes[0]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
require.ErrorIs(t, err, tc.err)
|
if tc.err == "" {
|
||||||
|
require.NoError(t, err)
|
||||||
|
} else {
|
||||||
|
require.ErrorContains(t, err, tc.err)
|
||||||
|
}
|
||||||
|
|
||||||
if tc.err == nil {
|
if tc.err == "" {
|
||||||
require.Equal(t, "slack receiver", res.Name)
|
require.Equal(t, "slack receiver", res.Name)
|
||||||
require.Len(t, res.GrafanaManagedReceivers, 1)
|
require.Len(t, res.GrafanaManagedReceivers, 1)
|
||||||
require.Equal(t, "UID2", res.GrafanaManagedReceivers[0].UID)
|
require.Equal(t, "UID2", res.GrafanaManagedReceivers[0].UID)
|
||||||
@ -183,15 +188,14 @@ func createReceiverServiceSut(t *testing.T, encryptSvc secrets.Service) *Receive
|
|||||||
xact := newNopTransactionManager()
|
xact := newNopTransactionManager()
|
||||||
provisioningStore := fakes.NewFakeProvisioningStore()
|
provisioningStore := fakes.NewFakeProvisioningStore()
|
||||||
|
|
||||||
return &ReceiverService{
|
return NewReceiverService(
|
||||||
ac: acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
||||||
provisioningStore: provisioningStore,
|
legacy_storage.NewAlertmanagerConfigStore(store),
|
||||||
cfgStore: store,
|
provisioningStore,
|
||||||
encryptionService: encryptSvc,
|
encryptSvc,
|
||||||
xact: xact,
|
xact,
|
||||||
log: log.NewNopLogger(),
|
log.NewNopLogger(),
|
||||||
validator: validation.ValidateProvenanceRelaxed,
|
)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func createEncryptedConfig(t *testing.T, secretService secrets.Service) string {
|
func createEncryptedConfig(t *testing.T, secretService secrets.Service) string {
|
||||||
|
@ -1,78 +0,0 @@
|
|||||||
package provisioning
|
|
||||||
|
|
||||||
import (
|
|
||||||
"context"
|
|
||||||
"encoding/json"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
||||||
)
|
|
||||||
|
|
||||||
func deserializeAlertmanagerConfig(config []byte) (*definitions.PostableUserConfig, error) {
|
|
||||||
result := definitions.PostableUserConfig{}
|
|
||||||
if err := json.Unmarshal(config, &result); err != nil {
|
|
||||||
return nil, makeErrBadAlertmanagerConfiguration(err)
|
|
||||||
}
|
|
||||||
return &result, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func serializeAlertmanagerConfig(config definitions.PostableUserConfig) ([]byte, error) {
|
|
||||||
return json.Marshal(config)
|
|
||||||
}
|
|
||||||
|
|
||||||
type cfgRevision struct {
|
|
||||||
cfg *definitions.PostableUserConfig
|
|
||||||
concurrencyToken string
|
|
||||||
version string
|
|
||||||
}
|
|
||||||
|
|
||||||
func getLastConfiguration(ctx context.Context, orgID int64, store AMConfigStore) (*cfgRevision, error) {
|
|
||||||
alertManagerConfig, err := store.GetLatestAlertmanagerConfiguration(ctx, orgID)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
if alertManagerConfig == nil {
|
|
||||||
return nil, ErrNoAlertmanagerConfiguration.Errorf("")
|
|
||||||
}
|
|
||||||
|
|
||||||
concurrencyToken := alertManagerConfig.ConfigurationHash
|
|
||||||
cfg, err := deserializeAlertmanagerConfig([]byte(alertManagerConfig.AlertmanagerConfiguration))
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
return &cfgRevision{
|
|
||||||
cfg: cfg,
|
|
||||||
concurrencyToken: concurrencyToken,
|
|
||||||
version: alertManagerConfig.ConfigurationVersion,
|
|
||||||
}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type alertmanagerConfigStore interface {
|
|
||||||
Get(ctx context.Context, orgID int64) (*cfgRevision, error)
|
|
||||||
Save(ctx context.Context, revision *cfgRevision, orgID int64) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type alertmanagerConfigStoreImpl struct {
|
|
||||||
store AMConfigStore
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a alertmanagerConfigStoreImpl) Get(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
|
||||||
return getLastConfiguration(ctx, orgID, a.store)
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a alertmanagerConfigStoreImpl) Save(ctx context.Context, revision *cfgRevision, orgID int64) error {
|
|
||||||
serialized, err := serializeAlertmanagerConfig(*revision.cfg)
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
cmd := models.SaveAlertmanagerConfigurationCmd{
|
|
||||||
AlertmanagerConfiguration: string(serialized),
|
|
||||||
ConfigurationVersion: revision.version,
|
|
||||||
FetchedConfigurationHash: revision.concurrencyToken,
|
|
||||||
Default: false,
|
|
||||||
OrgID: orgID,
|
|
||||||
}
|
|
||||||
return PersistConfig(ctx, a.store, &cmd)
|
|
||||||
}
|
|
@ -15,8 +15,8 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
"github.com/grafana/grafana/pkg/util"
|
"github.com/grafana/grafana/pkg/util"
|
||||||
@ -28,7 +28,7 @@ type AlertRuleNotificationSettingsStore interface {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ContactPointService struct {
|
type ContactPointService struct {
|
||||||
configStore *alertmanagerConfigStoreImpl
|
configStore alertmanagerConfigStore
|
||||||
encryptionService secrets.Service
|
encryptionService secrets.Service
|
||||||
provenanceStore ProvisioningStore
|
provenanceStore ProvisioningStore
|
||||||
notificationSettingsStore AlertRuleNotificationSettingsStore
|
notificationSettingsStore AlertRuleNotificationSettingsStore
|
||||||
@ -41,13 +41,11 @@ type receiverService interface {
|
|||||||
GetReceivers(ctx context.Context, query models.GetReceiversQuery, user identity.Requester) ([]apimodels.GettableApiReceiver, error)
|
GetReceivers(ctx context.Context, query models.GetReceiversQuery, user identity.Requester) ([]apimodels.GettableApiReceiver, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewContactPointService(store AMConfigStore, encryptionService secrets.Service,
|
func NewContactPointService(store alertmanagerConfigStore, encryptionService secrets.Service,
|
||||||
provenanceStore ProvisioningStore, xact TransactionManager, receiverService receiverService, log log.Logger,
|
provenanceStore ProvisioningStore, xact TransactionManager, receiverService receiverService, log log.Logger,
|
||||||
nsStore AlertRuleNotificationSettingsStore) *ContactPointService {
|
nsStore AlertRuleNotificationSettingsStore) *ContactPointService {
|
||||||
return &ContactPointService{
|
return &ContactPointService{
|
||||||
configStore: &alertmanagerConfigStoreImpl{
|
configStore: store,
|
||||||
store: store,
|
|
||||||
},
|
|
||||||
receiverService: receiverService,
|
receiverService: receiverService,
|
||||||
encryptionService: encryptionService,
|
encryptionService: encryptionService,
|
||||||
provenanceStore: provenanceStore,
|
provenanceStore: provenanceStore,
|
||||||
@ -118,7 +116,7 @@ func (ecp *ContactPointService) getContactPointDecrypted(ctx context.Context, or
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return apimodels.EmbeddedContactPoint{}, err
|
return apimodels.EmbeddedContactPoint{}, err
|
||||||
}
|
}
|
||||||
for _, receiver := range revision.cfg.GetGrafanaReceiverMap() {
|
for _, receiver := range revision.Config.GetGrafanaReceiverMap() {
|
||||||
if receiver.UID != uid {
|
if receiver.UID != uid {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
@ -180,7 +178,7 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in
|
|||||||
}
|
}
|
||||||
|
|
||||||
receiverFound := false
|
receiverFound := false
|
||||||
for _, receiver := range revision.cfg.AlertmanagerConfig.Receivers {
|
for _, receiver := range revision.Config.AlertmanagerConfig.Receivers {
|
||||||
// check if uid is already used in receiver
|
// check if uid is already used in receiver
|
||||||
for _, rec := range receiver.PostableGrafanaReceivers.GrafanaManagedReceivers {
|
for _, rec := range receiver.PostableGrafanaReceivers.GrafanaManagedReceivers {
|
||||||
if grafanaReceiver.UID == rec.UID {
|
if grafanaReceiver.UID == rec.UID {
|
||||||
@ -197,7 +195,7 @@ func (ecp *ContactPointService) CreateContactPoint(ctx context.Context, orgID in
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !receiverFound {
|
if !receiverFound {
|
||||||
revision.cfg.AlertmanagerConfig.Receivers = append(revision.cfg.AlertmanagerConfig.Receivers, &apimodels.PostableApiReceiver{
|
revision.Config.AlertmanagerConfig.Receivers = append(revision.Config.AlertmanagerConfig.Receivers, &apimodels.PostableApiReceiver{
|
||||||
Receiver: config.Receiver{
|
Receiver: config.Receiver{
|
||||||
Name: grafanaReceiver.Name,
|
Name: grafanaReceiver.Name,
|
||||||
},
|
},
|
||||||
@ -286,7 +284,7 @@ func (ecp *ContactPointService) UpdateContactPoint(ctx context.Context, orgID in
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
configModified, renamedReceiver := stitchReceiver(revision.cfg, mergedReceiver)
|
configModified, renamedReceiver := stitchReceiver(revision.Config, mergedReceiver)
|
||||||
if !configModified {
|
if !configModified {
|
||||||
return fmt.Errorf("contact point with uid '%s' not found", mergedReceiver.UID)
|
return fmt.Errorf("contact point with uid '%s' not found", mergedReceiver.UID)
|
||||||
}
|
}
|
||||||
@ -324,7 +322,7 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in
|
|||||||
// Name of the contact point that will be removed, might be used if a
|
// Name of the contact point that will be removed, might be used if a
|
||||||
// full removal is done to check if it's referenced in any route.
|
// full removal is done to check if it's referenced in any route.
|
||||||
name := ""
|
name := ""
|
||||||
for i, receiver := range revision.cfg.AlertmanagerConfig.Receivers {
|
for i, receiver := range revision.Config.AlertmanagerConfig.Receivers {
|
||||||
for j, grafanaReceiver := range receiver.GrafanaManagedReceivers {
|
for j, grafanaReceiver := range receiver.GrafanaManagedReceivers {
|
||||||
if grafanaReceiver.UID == uid {
|
if grafanaReceiver.UID == uid {
|
||||||
name = grafanaReceiver.Name
|
name = grafanaReceiver.Name
|
||||||
@ -332,13 +330,13 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in
|
|||||||
// if this was the last receiver we removed, we remove the whole receiver
|
// if this was the last receiver we removed, we remove the whole receiver
|
||||||
if len(receiver.GrafanaManagedReceivers) == 0 {
|
if len(receiver.GrafanaManagedReceivers) == 0 {
|
||||||
fullRemoval = true
|
fullRemoval = true
|
||||||
revision.cfg.AlertmanagerConfig.Receivers = append(revision.cfg.AlertmanagerConfig.Receivers[:i], revision.cfg.AlertmanagerConfig.Receivers[i+1:]...)
|
revision.Config.AlertmanagerConfig.Receivers = append(revision.Config.AlertmanagerConfig.Receivers[:i], revision.Config.AlertmanagerConfig.Receivers[i+1:]...)
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if fullRemoval && isContactPointInUse(name, []*apimodels.Route{revision.cfg.AlertmanagerConfig.Route}) {
|
if fullRemoval && revision.ReceiverNameUsedByRoutes(name) {
|
||||||
return ErrContactPointReferenced.Errorf("")
|
return ErrContactPointReferenced.Errorf("")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -368,21 +366,6 @@ func (ecp *ContactPointService) DeleteContactPoint(ctx context.Context, orgID in
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func isContactPointInUse(name string, routes []*apimodels.Route) bool {
|
|
||||||
if len(routes) == 0 {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
for _, route := range routes {
|
|
||||||
if route.Receiver == name {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if isContactPointInUse(name, route.Routes) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
// decryptValueOrRedacted returns a function that decodes a string from Base64 and then decrypts using secrets.Service.
|
// decryptValueOrRedacted returns a function that decodes a string from Base64 and then decrypts using secrets.Service.
|
||||||
// If argument 'decrypt' is false, then returns definitions.RedactedValue regardless of the decrypted value.
|
// If argument 'decrypt' is false, then returns definitions.RedactedValue regardless of the decrypted value.
|
||||||
// Otherwise, it returns the decoded and decrypted value. The function returns empty string in the case of errors, which are logged
|
// Otherwise, it returns the decoded and decrypted value. The function returns empty string in the case of errors, which are logged
|
||||||
@ -540,22 +523,10 @@ func RemoveSecretsForContactPoint(e *apimodels.EmbeddedContactPoint) (map[string
|
|||||||
return s, nil
|
return s, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// handleWrappedError unwraps an error and wraps it with a new expected error type. If the error is not wrapped, it returns just the expected error.
|
|
||||||
func handleWrappedError(err error, expected error) error {
|
|
||||||
err = errors.Unwrap(err)
|
|
||||||
if err == nil {
|
|
||||||
return expected
|
|
||||||
}
|
|
||||||
return fmt.Errorf("%w: %s", expected, err.Error())
|
|
||||||
}
|
|
||||||
|
|
||||||
// convertRecSvcErr converts errors from notifier.ReceiverService to errors expected from ContactPointService.
|
// convertRecSvcErr converts errors from notifier.ReceiverService to errors expected from ContactPointService.
|
||||||
func convertRecSvcErr(err error) error {
|
func convertRecSvcErr(err error) error {
|
||||||
if errors.Is(err, notifier.ErrPermissionDenied) {
|
|
||||||
return handleWrappedError(err, ErrPermissionDenied)
|
|
||||||
}
|
|
||||||
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
|
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
|
||||||
return ErrNoAlertmanagerConfiguration.Errorf("")
|
return legacy_storage.ErrNoAlertmanagerConfiguration.Errorf("")
|
||||||
}
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -18,9 +18,11 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
||||||
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
||||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets"
|
"github.com/grafana/grafana/pkg/services/secrets"
|
||||||
"github.com/grafana/grafana/pkg/services/secrets/database"
|
"github.com/grafana/grafana/pkg/services/secrets/database"
|
||||||
@ -249,17 +251,18 @@ func TestContactPointService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("service respects concurrency token when updating", func(t *testing.T) {
|
t.Run("service respects concurrency token when updating", func(t *testing.T) {
|
||||||
sut := createContactPointServiceSut(t, secretsService)
|
cfg := createEncryptedConfig(t, secretsService)
|
||||||
|
fakeConfigStore := fakes.NewFakeAlertmanagerConfigStore(cfg)
|
||||||
|
sut := createContactPointServiceSutWithConfigStore(t, secretsService, fakeConfigStore)
|
||||||
newCp := createTestContactPoint()
|
newCp := createTestContactPoint()
|
||||||
config, err := sut.configStore.store.GetLatestAlertmanagerConfiguration(context.Background(), 1)
|
config, err := sut.configStore.Get(context.Background(), 1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expectedConcurrencyToken := config.ConfigurationHash
|
expectedConcurrencyToken := config.ConcurrencyToken
|
||||||
|
|
||||||
_, err = sut.CreateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
|
_, err = sut.CreateContactPoint(context.Background(), 1, newCp, models.ProvenanceAPI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
fake := sut.configStore.store.(*fakes.FakeAlertmanagerConfigStore)
|
intercepted := fakeConfigStore.LastSaveCommand
|
||||||
intercepted := fake.LastSaveCommand
|
|
||||||
require.Equal(t, expectedConcurrencyToken, intercepted.FetchedConfigurationHash)
|
require.Equal(t, expectedConcurrencyToken, intercepted.FetchedConfigurationHash)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -296,7 +299,7 @@ func TestContactPointServiceDecryptRedact(t *testing.T) {
|
|||||||
q := cpsQuery(1)
|
q := cpsQuery(1)
|
||||||
q.Decrypt = true
|
q.Decrypt = true
|
||||||
_, err := sut.GetContactPoints(context.Background(), q, redactedUser)
|
_, err := sut.GetContactPoints(context.Background(), q, redactedUser)
|
||||||
require.ErrorIs(t, err, ErrPermissionDenied)
|
require.ErrorIs(t, err, ac.ErrAuthorizationBase)
|
||||||
})
|
})
|
||||||
t.Run("GetContactPoints errors when Decrypt = true and user is nil", func(t *testing.T) {
|
t.Run("GetContactPoints errors when Decrypt = true and user is nil", func(t *testing.T) {
|
||||||
sut := createContactPointServiceSut(t, secretsService)
|
sut := createContactPointServiceSut(t, secretsService)
|
||||||
@ -304,7 +307,7 @@ func TestContactPointServiceDecryptRedact(t *testing.T) {
|
|||||||
q := cpsQuery(1)
|
q := cpsQuery(1)
|
||||||
q.Decrypt = true
|
q.Decrypt = true
|
||||||
_, err := sut.GetContactPoints(context.Background(), q, nil)
|
_, err := sut.GetContactPoints(context.Background(), q, nil)
|
||||||
require.ErrorIs(t, err, ErrPermissionDenied)
|
require.ErrorIs(t, err, ac.ErrAuthorizationBase)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("GetContactPoints gets decrypted contact points when Decrypt = true and user has permissions", func(t *testing.T) {
|
t.Run("GetContactPoints gets decrypted contact points when Decrypt = true and user has permissions", func(t *testing.T) {
|
||||||
@ -322,47 +325,21 @@ func TestContactPointServiceDecryptRedact(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestContactPointInUse(t *testing.T) {
|
|
||||||
result := isContactPointInUse("test", []*definitions.Route{
|
|
||||||
{
|
|
||||||
Receiver: "not-test",
|
|
||||||
Routes: []*definitions.Route{
|
|
||||||
{
|
|
||||||
Receiver: "not-test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Receiver: "test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.True(t, result)
|
|
||||||
result = isContactPointInUse("test", []*definitions.Route{
|
|
||||||
{
|
|
||||||
Receiver: "not-test",
|
|
||||||
Routes: []*definitions.Route{
|
|
||||||
{
|
|
||||||
Receiver: "not-test",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
Receiver: "not-test",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
require.False(t, result)
|
|
||||||
}
|
|
||||||
|
|
||||||
func createContactPointServiceSut(t *testing.T, secretService secrets.Service) *ContactPointService {
|
func createContactPointServiceSut(t *testing.T, secretService secrets.Service) *ContactPointService {
|
||||||
// Encrypt secure settings.
|
// Encrypt secure settings.
|
||||||
cfg := createEncryptedConfig(t, secretService)
|
cfg := createEncryptedConfig(t, secretService)
|
||||||
store := fakes.NewFakeAlertmanagerConfigStore(cfg)
|
store := fakes.NewFakeAlertmanagerConfigStore(cfg)
|
||||||
|
return createContactPointServiceSutWithConfigStore(t, secretService, store)
|
||||||
|
}
|
||||||
|
|
||||||
|
func createContactPointServiceSutWithConfigStore(t *testing.T, secretService secrets.Service, configStore legacy_storage.AMConfigStore) *ContactPointService {
|
||||||
|
// Encrypt secure settings.
|
||||||
xact := newNopTransactionManager()
|
xact := newNopTransactionManager()
|
||||||
provisioningStore := fakes.NewFakeProvisioningStore()
|
provisioningStore := fakes.NewFakeProvisioningStore()
|
||||||
|
|
||||||
receiverService := notifier.NewReceiverService(
|
receiverService := notifier.NewReceiverService(
|
||||||
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
acimpl.ProvideAccessControl(featuremgmt.WithFeatures(), zanzana.NewNoopClient()),
|
||||||
store,
|
legacy_storage.NewAlertmanagerConfigStore(configStore),
|
||||||
provisioningStore,
|
provisioningStore,
|
||||||
secretService,
|
secretService,
|
||||||
xact,
|
xact,
|
||||||
@ -370,7 +347,7 @@ func createContactPointServiceSut(t *testing.T, secretService secrets.Service) *
|
|||||||
)
|
)
|
||||||
|
|
||||||
return &ContactPointService{
|
return &ContactPointService{
|
||||||
configStore: &alertmanagerConfigStoreImpl{store: store},
|
configStore: legacy_storage.NewAlertmanagerConfigStore(configStore),
|
||||||
provenanceStore: provisioningStore,
|
provenanceStore: provisioningStore,
|
||||||
receiverService: receiverService,
|
receiverService: receiverService,
|
||||||
xact: xact,
|
xact: xact,
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package provisioning
|
package provisioning
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
"github.com/grafana/grafana/pkg/apimachinery/errutil"
|
||||||
@ -10,12 +9,8 @@ import (
|
|||||||
|
|
||||||
var ErrValidation = fmt.Errorf("invalid object specification")
|
var ErrValidation = fmt.Errorf("invalid object specification")
|
||||||
var ErrNotFound = fmt.Errorf("object not found")
|
var ErrNotFound = fmt.Errorf("object not found")
|
||||||
var ErrPermissionDenied = errors.New("permission denied")
|
|
||||||
|
|
||||||
var (
|
var (
|
||||||
ErrNoAlertmanagerConfiguration = errutil.Internal("alerting.notification.configMissing", errutil.WithPublicMessage("No alertmanager configuration present in this organization"))
|
|
||||||
ErrBadAlertmanagerConfiguration = errutil.Internal("alerting.notification.configCorrupted").MustTemplate("Failed to unmarshal the Alertmanager configuration", errutil.WithPublic("Current Alertmanager configuration in the storage is corrupted. Reset the configuration or rollback to a recent valid one."))
|
|
||||||
|
|
||||||
ErrVersionConflict = errutil.Conflict("alerting.notifications.conflict")
|
ErrVersionConflict = errutil.Conflict("alerting.notifications.conflict")
|
||||||
|
|
||||||
ErrTimeIntervalNotFound = errutil.NotFound("alerting.notifications.time-intervals.notFound")
|
ErrTimeIntervalNotFound = errutil.NotFound("alerting.notifications.time-intervals.notFound")
|
||||||
@ -27,16 +22,6 @@ var (
|
|||||||
ErrContactPointUsedInRule = errutil.Conflict("alerting.notifications.contact-points.used-by-rule", errutil.WithPublicMessage("Contact point is currently used in the notification settings of one or many alert rules."))
|
ErrContactPointUsedInRule = errutil.Conflict("alerting.notifications.contact-points.used-by-rule", errutil.WithPublicMessage("Contact point is currently used in the notification settings of one or many alert rules."))
|
||||||
)
|
)
|
||||||
|
|
||||||
func makeErrBadAlertmanagerConfiguration(err error) error {
|
|
||||||
data := errutil.TemplateData{
|
|
||||||
Public: map[string]interface{}{
|
|
||||||
"Error": err.Error(),
|
|
||||||
},
|
|
||||||
Error: err,
|
|
||||||
}
|
|
||||||
return ErrBadAlertmanagerConfiguration.Build(data)
|
|
||||||
}
|
|
||||||
|
|
||||||
// MakeErrTimeIntervalInvalid creates an error with the ErrTimeIntervalInvalid template
|
// MakeErrTimeIntervalInvalid creates an error with the ErrTimeIntervalInvalid template
|
||||||
func MakeErrTimeIntervalInvalid(err error) error {
|
func MakeErrTimeIntervalInvalid(err error) error {
|
||||||
data := errutil.TemplateData{
|
data := errutil.TemplateData{
|
||||||
|
@ -2,7 +2,6 @@ package provisioning
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/base64"
|
|
||||||
"encoding/binary"
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -17,6 +16,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning/validation"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -29,9 +29,9 @@ type MuteTimingService struct {
|
|||||||
ruleNotificationsStore AlertRuleNotificationSettingsStore
|
ruleNotificationsStore AlertRuleNotificationSettingsStore
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewMuteTimingService(config AMConfigStore, prov ProvisioningStore, xact TransactionManager, log log.Logger, ns AlertRuleNotificationSettingsStore) *MuteTimingService {
|
func NewMuteTimingService(config alertmanagerConfigStore, prov ProvisioningStore, xact TransactionManager, log log.Logger, ns AlertRuleNotificationSettingsStore) *MuteTimingService {
|
||||||
return &MuteTimingService{
|
return &MuteTimingService{
|
||||||
configStore: &alertmanagerConfigStoreImpl{store: config},
|
configStore: config,
|
||||||
provenanceStore: prov,
|
provenanceStore: prov,
|
||||||
xact: xact,
|
xact: xact,
|
||||||
log: log,
|
log: log,
|
||||||
@ -47,7 +47,7 @@ func (svc *MuteTimingService) GetMuteTimings(ctx context.Context, orgID int64) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if rev.cfg.AlertmanagerConfig.MuteTimeIntervals == nil {
|
if rev.Config.AlertmanagerConfig.MuteTimeIntervals == nil {
|
||||||
return []definitions.MuteTimeInterval{}, nil
|
return []definitions.MuteTimeInterval{}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,11 +56,11 @@ func (svc *MuteTimingService) GetMuteTimings(ctx context.Context, orgID int64) (
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := make([]definitions.MuteTimeInterval, 0, len(rev.cfg.AlertmanagerConfig.MuteTimeIntervals))
|
result := make([]definitions.MuteTimeInterval, 0, len(rev.Config.AlertmanagerConfig.MuteTimeIntervals))
|
||||||
for _, interval := range rev.cfg.AlertmanagerConfig.MuteTimeIntervals {
|
for _, interval := range rev.Config.AlertmanagerConfig.MuteTimeIntervals {
|
||||||
version := calculateMuteTimeIntervalFingerprint(interval)
|
version := calculateMuteTimeIntervalFingerprint(interval)
|
||||||
def := definitions.MuteTimeInterval{
|
def := definitions.MuteTimeInterval{
|
||||||
UID: getIntervalUID(interval),
|
UID: legacy_storage.NameToUid(interval.Name),
|
||||||
MuteTimeInterval: interval,
|
MuteTimeInterval: interval,
|
||||||
Version: version,
|
Version: version,
|
||||||
}
|
}
|
||||||
@ -81,7 +81,7 @@ func (svc *MuteTimingService) GetMuteTiming(ctx context.Context, nameOrUID strin
|
|||||||
|
|
||||||
mt, idx := getMuteTimingByName(rev, nameOrUID)
|
mt, idx := getMuteTimingByName(rev, nameOrUID)
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
name, err := uidToName(nameOrUID)
|
name, err := legacy_storage.UidToName(nameOrUID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
mt, idx = getMuteTimingByName(rev, name)
|
mt, idx = getMuteTimingByName(rev, name)
|
||||||
}
|
}
|
||||||
@ -91,7 +91,7 @@ func (svc *MuteTimingService) GetMuteTiming(ctx context.Context, nameOrUID strin
|
|||||||
}
|
}
|
||||||
|
|
||||||
result := definitions.MuteTimeInterval{
|
result := definitions.MuteTimeInterval{
|
||||||
UID: getIntervalUID(mt),
|
UID: legacy_storage.NameToUid(mt.Name),
|
||||||
MuteTimeInterval: mt,
|
MuteTimeInterval: mt,
|
||||||
Version: calculateMuteTimeIntervalFingerprint(mt),
|
Version: calculateMuteTimeIntervalFingerprint(mt),
|
||||||
}
|
}
|
||||||
@ -119,7 +119,7 @@ func (svc *MuteTimingService) CreateMuteTiming(ctx context.Context, mt definitio
|
|||||||
if idx != -1 {
|
if idx != -1 {
|
||||||
return definitions.MuteTimeInterval{}, ErrTimeIntervalExists.Errorf("")
|
return definitions.MuteTimeInterval{}, ErrTimeIntervalExists.Errorf("")
|
||||||
}
|
}
|
||||||
revision.cfg.AlertmanagerConfig.MuteTimeIntervals = append(revision.cfg.AlertmanagerConfig.MuteTimeIntervals, mt.MuteTimeInterval)
|
revision.Config.AlertmanagerConfig.MuteTimeIntervals = append(revision.Config.AlertmanagerConfig.MuteTimeIntervals, mt.MuteTimeInterval)
|
||||||
|
|
||||||
err = svc.xact.InTransaction(ctx, func(ctx context.Context) error {
|
err = svc.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||||
if err := svc.configStore.Save(ctx, revision, orgID); err != nil {
|
if err := svc.configStore.Save(ctx, revision, orgID); err != nil {
|
||||||
@ -131,7 +131,7 @@ func (svc *MuteTimingService) CreateMuteTiming(ctx context.Context, mt definitio
|
|||||||
return definitions.MuteTimeInterval{}, err
|
return definitions.MuteTimeInterval{}, err
|
||||||
}
|
}
|
||||||
return definitions.MuteTimeInterval{
|
return definitions.MuteTimeInterval{
|
||||||
UID: getIntervalUID(mt.MuteTimeInterval),
|
UID: legacy_storage.NameToUid(mt.Name),
|
||||||
MuteTimeInterval: mt.MuteTimeInterval,
|
MuteTimeInterval: mt.MuteTimeInterval,
|
||||||
Version: calculateMuteTimeIntervalFingerprint(mt.MuteTimeInterval),
|
Version: calculateMuteTimeIntervalFingerprint(mt.MuteTimeInterval),
|
||||||
Provenance: mt.Provenance,
|
Provenance: mt.Provenance,
|
||||||
@ -152,7 +152,7 @@ func (svc *MuteTimingService) UpdateMuteTiming(ctx context.Context, mt definitio
|
|||||||
var old config.MuteTimeInterval
|
var old config.MuteTimeInterval
|
||||||
var idx = -1
|
var idx = -1
|
||||||
if mt.UID != "" {
|
if mt.UID != "" {
|
||||||
name, err := uidToName(mt.UID)
|
name, err := legacy_storage.UidToName(mt.UID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
old, idx = getMuteTimingByName(revision, name)
|
old, idx = getMuteTimingByName(revision, name)
|
||||||
}
|
}
|
||||||
@ -184,7 +184,7 @@ func (svc *MuteTimingService) UpdateMuteTiming(ctx context.Context, mt definitio
|
|||||||
return definitions.MuteTimeInterval{}, MakeErrTimeIntervalInvalid(errors.New("name change is not allowed"))
|
return definitions.MuteTimeInterval{}, MakeErrTimeIntervalInvalid(errors.New("name change is not allowed"))
|
||||||
}
|
}
|
||||||
|
|
||||||
revision.cfg.AlertmanagerConfig.MuteTimeIntervals[idx] = mt.MuteTimeInterval
|
revision.Config.AlertmanagerConfig.MuteTimeIntervals[idx] = mt.MuteTimeInterval
|
||||||
|
|
||||||
// TODO add diff and noop detection
|
// TODO add diff and noop detection
|
||||||
err = svc.xact.InTransaction(ctx, func(ctx context.Context) error {
|
err = svc.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||||
@ -197,7 +197,7 @@ func (svc *MuteTimingService) UpdateMuteTiming(ctx context.Context, mt definitio
|
|||||||
return definitions.MuteTimeInterval{}, err
|
return definitions.MuteTimeInterval{}, err
|
||||||
}
|
}
|
||||||
return definitions.MuteTimeInterval{
|
return definitions.MuteTimeInterval{
|
||||||
UID: getIntervalUID(mt.MuteTimeInterval),
|
UID: legacy_storage.NameToUid(mt.Name),
|
||||||
MuteTimeInterval: mt.MuteTimeInterval,
|
MuteTimeInterval: mt.MuteTimeInterval,
|
||||||
Version: calculateMuteTimeIntervalFingerprint(mt.MuteTimeInterval),
|
Version: calculateMuteTimeIntervalFingerprint(mt.MuteTimeInterval),
|
||||||
Provenance: mt.Provenance,
|
Provenance: mt.Provenance,
|
||||||
@ -213,7 +213,7 @@ func (svc *MuteTimingService) DeleteMuteTiming(ctx context.Context, nameOrUID st
|
|||||||
|
|
||||||
existing, idx := getMuteTimingByName(revision, nameOrUID)
|
existing, idx := getMuteTimingByName(revision, nameOrUID)
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
name, err := uidToName(nameOrUID)
|
name, err := legacy_storage.UidToName(nameOrUID)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
existing, idx = getMuteTimingByName(revision, name)
|
existing, idx = getMuteTimingByName(revision, name)
|
||||||
}
|
}
|
||||||
@ -233,7 +233,7 @@ func (svc *MuteTimingService) DeleteMuteTiming(ctx context.Context, nameOrUID st
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if isMuteTimeInUseInRoutes(existing.Name, revision.cfg.AlertmanagerConfig.Route) {
|
if isMuteTimeInUseInRoutes(existing.Name, revision.Config.AlertmanagerConfig.Route) {
|
||||||
ns, _ := svc.ruleNotificationsStore.ListNotificationSettings(ctx, models.ListNotificationSettingsQuery{OrgID: orgID, TimeIntervalName: existing.Name})
|
ns, _ := svc.ruleNotificationsStore.ListNotificationSettings(ctx, models.ListNotificationSettingsQuery{OrgID: orgID, TimeIntervalName: existing.Name})
|
||||||
// ignore error here because it's not important
|
// ignore error here because it's not important
|
||||||
return MakeErrTimeIntervalInUse(true, maps.Keys(ns))
|
return MakeErrTimeIntervalInUse(true, maps.Keys(ns))
|
||||||
@ -243,7 +243,7 @@ func (svc *MuteTimingService) DeleteMuteTiming(ctx context.Context, nameOrUID st
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
revision.cfg.AlertmanagerConfig.MuteTimeIntervals = slices.Delete(revision.cfg.AlertmanagerConfig.MuteTimeIntervals, idx, idx+1)
|
revision.Config.AlertmanagerConfig.MuteTimeIntervals = slices.Delete(revision.Config.AlertmanagerConfig.MuteTimeIntervals, idx, idx+1)
|
||||||
|
|
||||||
return svc.xact.InTransaction(ctx, func(ctx context.Context) error {
|
return svc.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||||
keys, err := svc.ruleNotificationsStore.ListNotificationSettings(ctx, models.ListNotificationSettingsQuery{OrgID: orgID, TimeIntervalName: existing.Name})
|
keys, err := svc.ruleNotificationsStore.ListNotificationSettings(ctx, models.ListNotificationSettingsQuery{OrgID: orgID, TimeIntervalName: existing.Name})
|
||||||
@ -276,14 +276,14 @@ func isMuteTimeInUseInRoutes(name string, route *definitions.Route) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func getMuteTimingByName(rev *cfgRevision, name string) (config.MuteTimeInterval, int) {
|
func getMuteTimingByName(rev *legacy_storage.ConfigRevision, name string) (config.MuteTimeInterval, int) {
|
||||||
idx := slices.IndexFunc(rev.cfg.AlertmanagerConfig.MuteTimeIntervals, func(interval config.MuteTimeInterval) bool {
|
idx := slices.IndexFunc(rev.Config.AlertmanagerConfig.MuteTimeIntervals, func(interval config.MuteTimeInterval) bool {
|
||||||
return interval.Name == name
|
return interval.Name == name
|
||||||
})
|
})
|
||||||
if idx == -1 {
|
if idx == -1 {
|
||||||
return config.MuteTimeInterval{}, idx
|
return config.MuteTimeInterval{}, idx
|
||||||
}
|
}
|
||||||
return rev.cfg.AlertmanagerConfig.MuteTimeIntervals[idx], idx
|
return rev.Config.AlertmanagerConfig.MuteTimeIntervals[idx], idx
|
||||||
}
|
}
|
||||||
|
|
||||||
func calculateMuteTimeIntervalFingerprint(interval config.MuteTimeInterval) string {
|
func calculateMuteTimeIntervalFingerprint(interval config.MuteTimeInterval) string {
|
||||||
@ -355,15 +355,3 @@ func (svc *MuteTimingService) checkOptimisticConcurrency(current config.MuteTime
|
|||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func getIntervalUID(t config.MuteTimeInterval) string {
|
|
||||||
return base64.RawURLEncoding.EncodeToString([]byte(t.Name))
|
|
||||||
}
|
|
||||||
|
|
||||||
func uidToName(uid string) (string, error) {
|
|
||||||
data, err := base64.RawURLEncoding.DecodeString(uid)
|
|
||||||
if err != nil {
|
|
||||||
return uid, err
|
|
||||||
}
|
|
||||||
return string(data), nil
|
|
||||||
}
|
|
||||||
|
@ -16,12 +16,13 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestGetMuteTimings(t *testing.T) {
|
func TestGetMuteTimings(t *testing.T) {
|
||||||
orgID := int64(1)
|
orgID := int64(1)
|
||||||
revision := &cfgRevision{
|
revision := &legacy_storage.ConfigRevision{
|
||||||
cfg: &definitions.PostableUserConfig{
|
Config: &definitions.PostableUserConfig{
|
||||||
AlertmanagerConfig: definitions.PostableApiAlertingConfig{
|
AlertmanagerConfig: definitions.PostableApiAlertingConfig{
|
||||||
Config: definitions.Config{
|
Config: definitions.Config{
|
||||||
MuteTimeIntervals: []config.MuteTimeInterval{
|
MuteTimeIntervals: []config.MuteTimeInterval{
|
||||||
@ -50,7 +51,7 @@ func TestGetMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("service returns timings from config file", func(t *testing.T) {
|
t.Run("service returns timings from config file", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return revision, nil
|
return revision, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,21 +60,21 @@ func TestGetMuteTimings(t *testing.T) {
|
|||||||
result, err := sut.GetMuteTimings(context.Background(), 1)
|
result, err := sut.GetMuteTimings(context.Background(), 1)
|
||||||
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, result, len(revision.cfg.AlertmanagerConfig.MuteTimeIntervals))
|
require.Len(t, result, len(revision.Config.AlertmanagerConfig.MuteTimeIntervals))
|
||||||
require.Equal(t, "Test1", result[0].Name)
|
require.Equal(t, "Test1", result[0].Name)
|
||||||
require.EqualValues(t, provenances["Test1"], result[0].Provenance)
|
require.EqualValues(t, provenances["Test1"], result[0].Provenance)
|
||||||
require.NotEmpty(t, result[0].Version)
|
require.NotEmpty(t, result[0].Version)
|
||||||
require.Equal(t, getIntervalUID(result[0].MuteTimeInterval), result[0].UID)
|
require.Equal(t, legacy_storage.NameToUid(result[0].Name), result[0].UID)
|
||||||
|
|
||||||
require.Equal(t, "Test2", result[1].Name)
|
require.Equal(t, "Test2", result[1].Name)
|
||||||
require.EqualValues(t, provenances["Test2"], result[1].Provenance)
|
require.EqualValues(t, provenances["Test2"], result[1].Provenance)
|
||||||
require.NotEmpty(t, result[1].Version)
|
require.NotEmpty(t, result[1].Version)
|
||||||
require.Equal(t, getIntervalUID(result[1].MuteTimeInterval), result[1].UID)
|
require.Equal(t, legacy_storage.NameToUid(result[1].Name), result[1].UID)
|
||||||
|
|
||||||
require.Equal(t, "Test3", result[2].Name)
|
require.Equal(t, "Test3", result[2].Name)
|
||||||
require.EqualValues(t, "", result[2].Provenance)
|
require.EqualValues(t, "", result[2].Provenance)
|
||||||
require.NotEmpty(t, result[2].Version)
|
require.NotEmpty(t, result[2].Version)
|
||||||
require.Equal(t, getIntervalUID(result[2].MuteTimeInterval), result[2].UID)
|
require.Equal(t, legacy_storage.NameToUid(result[2].Name), result[2].UID)
|
||||||
|
|
||||||
require.Len(t, store.Calls, 1)
|
require.Len(t, store.Calls, 1)
|
||||||
require.Equal(t, "Get", store.Calls[0].Method)
|
require.Equal(t, "Get", store.Calls[0].Method)
|
||||||
@ -84,8 +85,8 @@ func TestGetMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("service returns empty list when config file contains no mute timings", func(t *testing.T) {
|
t.Run("service returns empty list when config file contains no mute timings", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: &definitions.PostableUserConfig{}}, nil
|
return &legacy_storage.ConfigRevision{Config: &definitions.PostableUserConfig{}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
result, err := sut.GetMuteTimings(context.Background(), 1)
|
result, err := sut.GetMuteTimings(context.Background(), 1)
|
||||||
@ -98,7 +99,7 @@ func TestGetMuteTimings(t *testing.T) {
|
|||||||
t.Run("when unable to read config", func(t *testing.T) {
|
t.Run("when unable to read config", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
expected := fmt.Errorf("failed")
|
expected := fmt.Errorf("failed")
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return nil, expected
|
return nil, expected
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -109,7 +110,7 @@ func TestGetMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("when unable to read provenance", func(t *testing.T) {
|
t.Run("when unable to read provenance", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return revision, nil
|
return revision, nil
|
||||||
}
|
}
|
||||||
expected := fmt.Errorf("failed")
|
expected := fmt.Errorf("failed")
|
||||||
@ -124,8 +125,8 @@ func TestGetMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
func TestGetMuteTiming(t *testing.T) {
|
func TestGetMuteTiming(t *testing.T) {
|
||||||
orgID := int64(1)
|
orgID := int64(1)
|
||||||
revision := &cfgRevision{
|
revision := &legacy_storage.ConfigRevision{
|
||||||
cfg: &definitions.PostableUserConfig{
|
Config: &definitions.PostableUserConfig{
|
||||||
AlertmanagerConfig: definitions.PostableApiAlertingConfig{
|
AlertmanagerConfig: definitions.PostableApiAlertingConfig{
|
||||||
Config: definitions.Config{
|
Config: definitions.Config{
|
||||||
MuteTimeIntervals: []config.MuteTimeInterval{
|
MuteTimeIntervals: []config.MuteTimeInterval{
|
||||||
@ -141,7 +142,7 @@ func TestGetMuteTiming(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("service returns timing by name", func(t *testing.T) {
|
t.Run("service returns timing by name", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return revision, nil
|
return revision, nil
|
||||||
}
|
}
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
||||||
@ -152,7 +153,7 @@ func TestGetMuteTiming(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, "Test1", result.Name)
|
require.Equal(t, "Test1", result.Name)
|
||||||
require.EqualValues(t, models.ProvenanceAPI, result.Provenance)
|
require.EqualValues(t, models.ProvenanceAPI, result.Provenance)
|
||||||
require.Equal(t, getIntervalUID(result.MuteTimeInterval), result.UID)
|
require.Equal(t, legacy_storage.NameToUid(result.Name), result.UID)
|
||||||
require.NotEmpty(t, result.Version)
|
require.NotEmpty(t, result.Version)
|
||||||
|
|
||||||
require.Len(t, store.Calls, 1)
|
require.Len(t, store.Calls, 1)
|
||||||
@ -172,8 +173,8 @@ func TestGetMuteTiming(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("service returns ErrTimeIntervalNotFound if no mute timings", func(t *testing.T) {
|
t.Run("service returns ErrTimeIntervalNotFound if no mute timings", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: &definitions.PostableUserConfig{}}, nil
|
return &legacy_storage.ConfigRevision{Config: &definitions.PostableUserConfig{}}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err := sut.GetMuteTiming(context.Background(), "Test1", orgID)
|
_, err := sut.GetMuteTiming(context.Background(), "Test1", orgID)
|
||||||
@ -183,7 +184,7 @@ func TestGetMuteTiming(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("service returns ErrTimeIntervalNotFound if no mute timing by name", func(t *testing.T) {
|
t.Run("service returns ErrTimeIntervalNotFound if no mute timing by name", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return revision, nil
|
return revision, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -196,7 +197,7 @@ func TestGetMuteTiming(t *testing.T) {
|
|||||||
t.Run("when unable to read config", func(t *testing.T) {
|
t.Run("when unable to read config", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
expected := fmt.Errorf("failed")
|
expected := fmt.Errorf("failed")
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return nil, expected
|
return nil, expected
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -207,7 +208,7 @@ func TestGetMuteTiming(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("when unable to read provenance", func(t *testing.T) {
|
t.Run("when unable to read provenance", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return revision, nil
|
return revision, nil
|
||||||
}
|
}
|
||||||
expected := fmt.Errorf("failed")
|
expected := fmt.Errorf("failed")
|
||||||
@ -273,8 +274,8 @@ func TestCreateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returns ErrTimeIntervalExists if mute timing with the name exists", func(t *testing.T) {
|
t.Run("returns ErrTimeIntervalExists if mute timing with the name exists", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
existing := initialConfig().AlertmanagerConfig.MuteTimeIntervals[0]
|
existing := initialConfig().AlertmanagerConfig.MuteTimeIntervals[0]
|
||||||
@ -290,10 +291,10 @@ func TestCreateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("saves mute timing and provenance in a transaction", func(t *testing.T) {
|
t.Run("saves mute timing and provenance in a transaction", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
store.SaveFn = func(ctx context.Context, revision *cfgRevision) error {
|
store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error {
|
||||||
assertInTransaction(t, ctx)
|
assertInTransaction(t, ctx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -308,7 +309,7 @@ func TestCreateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
require.EqualValues(t, expected, result.MuteTimeInterval)
|
require.EqualValues(t, expected, result.MuteTimeInterval)
|
||||||
require.EqualValues(t, expectedProvenance, result.Provenance)
|
require.EqualValues(t, expectedProvenance, result.Provenance)
|
||||||
require.Equal(t, getIntervalUID(expected), result.UID)
|
require.Equal(t, legacy_storage.NameToUid(expected.Name), result.UID)
|
||||||
require.NotEmpty(t, result.Version)
|
require.NotEmpty(t, result.Version)
|
||||||
|
|
||||||
require.Len(t, store.Calls, 2)
|
require.Len(t, store.Calls, 2)
|
||||||
@ -317,10 +318,10 @@ func TestCreateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, "Save", store.Calls[1].Method)
|
require.Equal(t, "Save", store.Calls[1].Method)
|
||||||
require.Equal(t, orgID, store.Calls[1].Args[2])
|
require.Equal(t, orgID, store.Calls[1].Args[2])
|
||||||
revision := store.Calls[1].Args[1].(*cfgRevision)
|
revision := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision)
|
||||||
|
|
||||||
expectedTimings := append(initialConfig().AlertmanagerConfig.MuteTimeIntervals, expected)
|
expectedTimings := append(initialConfig().AlertmanagerConfig.MuteTimeIntervals, expected)
|
||||||
require.EqualValues(t, expectedTimings, revision.cfg.AlertmanagerConfig.MuteTimeIntervals)
|
require.EqualValues(t, expectedTimings, revision.Config.AlertmanagerConfig.MuteTimeIntervals)
|
||||||
|
|
||||||
prov.AssertCalled(t, "SetProvenance", mock.Anything, &timing, orgID, expectedProvenance)
|
prov.AssertCalled(t, "SetProvenance", mock.Anything, &timing, orgID, expectedProvenance)
|
||||||
})
|
})
|
||||||
@ -329,7 +330,7 @@ func TestCreateMuteTimings(t *testing.T) {
|
|||||||
t.Run("when unable to read config", func(t *testing.T) {
|
t.Run("when unable to read config", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
expectedErr := errors.New("test-err")
|
expectedErr := errors.New("test-err")
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return nil, expectedErr
|
return nil, expectedErr
|
||||||
}
|
}
|
||||||
_, err := sut.CreateMuteTiming(context.Background(), timing, orgID)
|
_, err := sut.CreateMuteTiming(context.Background(), timing, orgID)
|
||||||
@ -338,8 +339,8 @@ func TestCreateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("when provenance fails to save", func(t *testing.T) {
|
t.Run("when provenance fails to save", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
expectedErr := fmt.Errorf("failed to save provenance")
|
expectedErr := fmt.Errorf("failed to save provenance")
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
||||||
@ -359,11 +360,11 @@ func TestCreateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("when AM config fails to save", func(t *testing.T) {
|
t.Run("when AM config fails to save", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
expectedErr := errors.New("test-err")
|
expectedErr := errors.New("test-err")
|
||||||
store.SaveFn = func(ctx context.Context, revision *cfgRevision) error {
|
store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error {
|
||||||
return expectedErr
|
return expectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -417,7 +418,7 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
}
|
}
|
||||||
expectedProvenance := models.ProvenanceAPI
|
expectedProvenance := models.ProvenanceAPI
|
||||||
expectedVersion := calculateMuteTimeIntervalFingerprint(expected)
|
expectedVersion := calculateMuteTimeIntervalFingerprint(expected)
|
||||||
expectedUID := getIntervalUID(expected)
|
expectedUID := legacy_storage.NameToUid(expected.Name)
|
||||||
timing := definitions.MuteTimeInterval{
|
timing := definitions.MuteTimeInterval{
|
||||||
MuteTimeInterval: expected,
|
MuteTimeInterval: expected,
|
||||||
Version: originalVersion,
|
Version: originalVersion,
|
||||||
@ -440,8 +441,8 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("rejects mute timings if provenance is not right", func(t *testing.T) {
|
t.Run("rejects mute timings if provenance is not right", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
expectedErr := errors.New("test")
|
expectedErr := errors.New("test")
|
||||||
sut.validator = func(from, to models.Provenance) error {
|
sut.validator = func(from, to models.Provenance) error {
|
||||||
@ -461,8 +462,8 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("rejects if mute timing is renamed", func(t *testing.T) {
|
t.Run("rejects if mute timing is renamed", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedProvenance, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedProvenance, nil)
|
||||||
|
|
||||||
@ -481,8 +482,8 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("rejects mute timings if provenance is not right", func(t *testing.T) {
|
t.Run("rejects mute timings if provenance is not right", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
expectedErr := errors.New("test")
|
expectedErr := errors.New("test")
|
||||||
sut.validator = func(from, to models.Provenance) error {
|
sut.validator = func(from, to models.Provenance) error {
|
||||||
@ -502,8 +503,8 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returns ErrVersionConflict if storage version does not match", func(t *testing.T) {
|
t.Run("returns ErrVersionConflict if storage version does not match", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
timing := definitions.MuteTimeInterval{
|
timing := definitions.MuteTimeInterval{
|
||||||
@ -521,8 +522,8 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returns ErrMuteTimingsNotFound if mute timing does not exist", func(t *testing.T) {
|
t.Run("returns ErrMuteTimingsNotFound if mute timing does not exist", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedProvenance, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedProvenance, nil)
|
||||||
timing := definitions.MuteTimeInterval{
|
timing := definitions.MuteTimeInterval{
|
||||||
@ -539,8 +540,8 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returns ErrMuteTimingsNotFound if mute timing does not exist", func(t *testing.T) {
|
t.Run("returns ErrMuteTimingsNotFound if mute timing does not exist", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedProvenance, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedProvenance, nil)
|
||||||
|
|
||||||
@ -572,10 +573,10 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("saves mute timing and provenance in a transaction if optimistic concurrency passes", func(t *testing.T) {
|
t.Run("saves mute timing and provenance in a transaction if optimistic concurrency passes", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
store.SaveFn = func(ctx context.Context, revision *cfgRevision) error {
|
store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error {
|
||||||
assertInTransaction(t, ctx)
|
assertInTransaction(t, ctx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -592,7 +593,7 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
require.EqualValues(t, expected, result.MuteTimeInterval)
|
require.EqualValues(t, expected, result.MuteTimeInterval)
|
||||||
require.EqualValues(t, expectedProvenance, result.Provenance)
|
require.EqualValues(t, expectedProvenance, result.Provenance)
|
||||||
require.EqualValues(t, expectedVersion, result.Version)
|
require.EqualValues(t, expectedVersion, result.Version)
|
||||||
require.Equal(t, getIntervalUID(result.MuteTimeInterval), result.UID)
|
require.Equal(t, legacy_storage.NameToUid(result.Name), result.UID)
|
||||||
|
|
||||||
require.Len(t, store.Calls, 2)
|
require.Len(t, store.Calls, 2)
|
||||||
require.Equal(t, "Get", store.Calls[0].Method)
|
require.Equal(t, "Get", store.Calls[0].Method)
|
||||||
@ -600,9 +601,9 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, "Save", store.Calls[1].Method)
|
require.Equal(t, "Save", store.Calls[1].Method)
|
||||||
require.Equal(t, orgID, store.Calls[1].Args[2])
|
require.Equal(t, orgID, store.Calls[1].Args[2])
|
||||||
revision := store.Calls[1].Args[1].(*cfgRevision)
|
revision := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision)
|
||||||
|
|
||||||
require.EqualValues(t, []config.MuteTimeInterval{expected}, revision.cfg.AlertmanagerConfig.MuteTimeIntervals)
|
require.EqualValues(t, []config.MuteTimeInterval{expected}, revision.Config.AlertmanagerConfig.MuteTimeIntervals)
|
||||||
|
|
||||||
prov.AssertCalled(t, "SetProvenance", mock.Anything, &timing, orgID, expectedProvenance)
|
prov.AssertCalled(t, "SetProvenance", mock.Anything, &timing, orgID, expectedProvenance)
|
||||||
|
|
||||||
@ -636,9 +637,9 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, "Save", store.Calls[1].Method)
|
require.Equal(t, "Save", store.Calls[1].Method)
|
||||||
require.Equal(t, orgID, store.Calls[1].Args[2])
|
require.Equal(t, orgID, store.Calls[1].Args[2])
|
||||||
revision := store.Calls[1].Args[1].(*cfgRevision)
|
revision := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision)
|
||||||
|
|
||||||
require.EqualValues(t, []config.MuteTimeInterval{timing.MuteTimeInterval}, revision.cfg.AlertmanagerConfig.MuteTimeIntervals)
|
require.EqualValues(t, []config.MuteTimeInterval{timing.MuteTimeInterval}, revision.Config.AlertmanagerConfig.MuteTimeIntervals)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -647,7 +648,7 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
expectedErr := errors.New("test-err")
|
expectedErr := errors.New("test-err")
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedProvenance, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(expectedProvenance, nil)
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return nil, expectedErr
|
return nil, expectedErr
|
||||||
}
|
}
|
||||||
_, err := sut.UpdateMuteTiming(context.Background(), timing, orgID)
|
_, err := sut.UpdateMuteTiming(context.Background(), timing, orgID)
|
||||||
@ -656,8 +657,8 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("when provenance fails to save", func(t *testing.T) {
|
t.Run("when provenance fails to save", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
expectedErr := fmt.Errorf("failed to save provenance")
|
expectedErr := fmt.Errorf("failed to save provenance")
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
||||||
@ -680,14 +681,14 @@ func TestUpdateMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("when AM config fails to save", func(t *testing.T) {
|
t.Run("when AM config fails to save", func(t *testing.T) {
|
||||||
sut, store, _ := createMuteTimingSvcSut()
|
sut, store, _ := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
||||||
GetProvenance(mock.Anything, mock.Anything, mock.Anything).
|
GetProvenance(mock.Anything, mock.Anything, mock.Anything).
|
||||||
Return(expectedProvenance, nil)
|
Return(expectedProvenance, nil)
|
||||||
expectedErr := errors.New("test-err")
|
expectedErr := errors.New("test-err")
|
||||||
store.SaveFn = func(ctx context.Context, revision *cfgRevision) error {
|
store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error {
|
||||||
return expectedErr
|
return expectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -736,8 +737,8 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
sut.validator = func(from, to models.Provenance) error {
|
sut.validator = func(from, to models.Provenance) error {
|
||||||
return expectedErr
|
return expectedErr
|
||||||
}
|
}
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
||||||
|
|
||||||
@ -747,8 +748,8 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returns ErrTimeIntervalInUse if mute timing is used by a route", func(t *testing.T) {
|
t.Run("returns ErrTimeIntervalInUse if mute timing is used by a route", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
||||||
|
|
||||||
@ -774,8 +775,8 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
sut.ruleNotificationsStore = &ruleNsStore
|
sut.ruleNotificationsStore = &ruleNsStore
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
||||||
|
|
||||||
@ -791,8 +792,8 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("returns ErrVersionConflict if provided version does not match", func(t *testing.T) {
|
t.Run("returns ErrVersionConflict if provided version does not match", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceAPI, nil)
|
||||||
|
|
||||||
@ -806,10 +807,10 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("deletes mute timing and provenance in transaction if passes optimistic concurrency check", func(t *testing.T) {
|
t.Run("deletes mute timing and provenance in transaction if passes optimistic concurrency check", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
store.SaveFn = func(ctx context.Context, revision *cfgRevision) error {
|
store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error {
|
||||||
assertInTransaction(t, ctx)
|
assertInTransaction(t, ctx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -829,12 +830,12 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, "Save", store.Calls[1].Method)
|
require.Equal(t, "Save", store.Calls[1].Method)
|
||||||
require.Equal(t, orgID, store.Calls[1].Args[2])
|
require.Equal(t, orgID, store.Calls[1].Args[2])
|
||||||
revision := store.Calls[1].Args[1].(*cfgRevision)
|
revision := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision)
|
||||||
|
|
||||||
expectedMuteTimings := slices.DeleteFunc(initialConfig().AlertmanagerConfig.MuteTimeIntervals, func(interval config.MuteTimeInterval) bool {
|
expectedMuteTimings := slices.DeleteFunc(initialConfig().AlertmanagerConfig.MuteTimeIntervals, func(interval config.MuteTimeInterval) bool {
|
||||||
return interval.Name == timingToDelete.Name
|
return interval.Name == timingToDelete.Name
|
||||||
})
|
})
|
||||||
require.EqualValues(t, expectedMuteTimings, revision.cfg.AlertmanagerConfig.MuteTimeIntervals)
|
require.EqualValues(t, expectedMuteTimings, revision.Config.AlertmanagerConfig.MuteTimeIntervals)
|
||||||
|
|
||||||
prov.AssertCalled(t, "DeleteProvenance", mock.Anything, &definitions.MuteTimeInterval{MuteTimeInterval: timingToDelete}, orgID)
|
prov.AssertCalled(t, "DeleteProvenance", mock.Anything, &definitions.MuteTimeInterval{MuteTimeInterval: timingToDelete}, orgID)
|
||||||
|
|
||||||
@ -845,12 +846,12 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, "Save", store.Calls[1].Method)
|
require.Equal(t, "Save", store.Calls[1].Method)
|
||||||
require.Equal(t, orgID, store.Calls[1].Args[2])
|
require.Equal(t, orgID, store.Calls[1].Args[2])
|
||||||
revision := store.Calls[1].Args[1].(*cfgRevision)
|
revision := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision)
|
||||||
|
|
||||||
expectedMuteTimings := slices.DeleteFunc(initialConfig().AlertmanagerConfig.MuteTimeIntervals, func(interval config.MuteTimeInterval) bool {
|
expectedMuteTimings := slices.DeleteFunc(initialConfig().AlertmanagerConfig.MuteTimeIntervals, func(interval config.MuteTimeInterval) bool {
|
||||||
return interval.Name == timingToDelete.Name
|
return interval.Name == timingToDelete.Name
|
||||||
})
|
})
|
||||||
require.EqualValues(t, expectedMuteTimings, revision.cfg.AlertmanagerConfig.MuteTimeIntervals)
|
require.EqualValues(t, expectedMuteTimings, revision.Config.AlertmanagerConfig.MuteTimeIntervals)
|
||||||
|
|
||||||
prov.AssertCalled(t, "DeleteProvenance", mock.Anything, &definitions.MuteTimeInterval{MuteTimeInterval: timingToDelete}, orgID)
|
prov.AssertCalled(t, "DeleteProvenance", mock.Anything, &definitions.MuteTimeInterval{MuteTimeInterval: timingToDelete}, orgID)
|
||||||
})
|
})
|
||||||
@ -858,10 +859,10 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("deletes mute timing and provenance by UID", func(t *testing.T) {
|
t.Run("deletes mute timing and provenance by UID", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
store.SaveFn = func(ctx context.Context, revision *cfgRevision) error {
|
store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error {
|
||||||
assertInTransaction(t, ctx)
|
assertInTransaction(t, ctx)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -872,7 +873,7 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
return nil
|
return nil
|
||||||
})
|
})
|
||||||
|
|
||||||
uid := getIntervalUID(timingToDelete)
|
uid := legacy_storage.NameToUid(timingToDelete.Name)
|
||||||
|
|
||||||
err := sut.DeleteMuteTiming(context.Background(), uid, orgID, "", correctVersion)
|
err := sut.DeleteMuteTiming(context.Background(), uid, orgID, "", correctVersion)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
@ -883,12 +884,12 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
|
|
||||||
require.Equal(t, "Save", store.Calls[1].Method)
|
require.Equal(t, "Save", store.Calls[1].Method)
|
||||||
require.Equal(t, orgID, store.Calls[1].Args[2])
|
require.Equal(t, orgID, store.Calls[1].Args[2])
|
||||||
revision := store.Calls[1].Args[1].(*cfgRevision)
|
revision := store.Calls[1].Args[1].(*legacy_storage.ConfigRevision)
|
||||||
|
|
||||||
expectedMuteTimings := slices.DeleteFunc(initialConfig().AlertmanagerConfig.MuteTimeIntervals, func(interval config.MuteTimeInterval) bool {
|
expectedMuteTimings := slices.DeleteFunc(initialConfig().AlertmanagerConfig.MuteTimeIntervals, func(interval config.MuteTimeInterval) bool {
|
||||||
return interval.Name == timingToDelete.Name
|
return interval.Name == timingToDelete.Name
|
||||||
})
|
})
|
||||||
require.EqualValues(t, expectedMuteTimings, revision.cfg.AlertmanagerConfig.MuteTimeIntervals)
|
require.EqualValues(t, expectedMuteTimings, revision.Config.AlertmanagerConfig.MuteTimeIntervals)
|
||||||
|
|
||||||
prov.AssertCalled(t, "DeleteProvenance", mock.Anything, &definitions.MuteTimeInterval{MuteTimeInterval: timingToDelete}, orgID)
|
prov.AssertCalled(t, "DeleteProvenance", mock.Anything, &definitions.MuteTimeInterval{MuteTimeInterval: timingToDelete}, orgID)
|
||||||
})
|
})
|
||||||
@ -898,7 +899,7 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
expectedErr := errors.New("test-err")
|
expectedErr := errors.New("test-err")
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return nil, expectedErr
|
return nil, expectedErr
|
||||||
}
|
}
|
||||||
err := sut.DeleteMuteTiming(context.Background(), timingToDelete.Name, orgID, "", "")
|
err := sut.DeleteMuteTiming(context.Background(), timingToDelete.Name, orgID, "", "")
|
||||||
@ -908,8 +909,8 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
t.Run("when provenance fails to save", func(t *testing.T) {
|
t.Run("when provenance fails to save", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
expectedErr := fmt.Errorf("failed to save provenance")
|
expectedErr := fmt.Errorf("failed to save provenance")
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
||||||
@ -930,11 +931,11 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
t.Run("when AM config fails to save", func(t *testing.T) {
|
t.Run("when AM config fails to save", func(t *testing.T) {
|
||||||
sut, store, prov := createMuteTimingSvcSut()
|
sut, store, prov := createMuteTimingSvcSut()
|
||||||
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
prov.EXPECT().GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(models.ProvenanceNone, nil)
|
||||||
store.GetFn = func(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
store.GetFn = func(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error) {
|
||||||
return &cfgRevision{cfg: initialConfig()}, nil
|
return &legacy_storage.ConfigRevision{Config: initialConfig()}, nil
|
||||||
}
|
}
|
||||||
expectedErr := errors.New("test-err")
|
expectedErr := errors.New("test-err")
|
||||||
store.SaveFn = func(ctx context.Context, revision *cfgRevision) error {
|
store.SaveFn = func(ctx context.Context, revision *legacy_storage.ConfigRevision) error {
|
||||||
return expectedErr
|
return expectedErr
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -951,8 +952,8 @@ func TestDeleteMuteTimings(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMuteTimingSvcSut() (*MuteTimingService, *alertmanagerConfigStoreFake, *MockProvisioningStore) {
|
func createMuteTimingSvcSut() (*MuteTimingService, *legacy_storage.AlertmanagerConfigStoreFake, *MockProvisioningStore) {
|
||||||
store := &alertmanagerConfigStoreFake{}
|
store := &legacy_storage.AlertmanagerConfigStoreFake{}
|
||||||
prov := &MockProvisioningStore{}
|
prov := &MockProvisioningStore{}
|
||||||
return &MuteTimingService{
|
return &MuteTimingService{
|
||||||
configStore: store,
|
configStore: store,
|
||||||
|
@ -7,21 +7,22 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
type NotificationPolicyService struct {
|
type NotificationPolicyService struct {
|
||||||
configStore *alertmanagerConfigStoreImpl
|
configStore alertmanagerConfigStore
|
||||||
provenanceStore ProvisioningStore
|
provenanceStore ProvisioningStore
|
||||||
xact TransactionManager
|
xact TransactionManager
|
||||||
log log.Logger
|
log log.Logger
|
||||||
settings setting.UnifiedAlertingSettings
|
settings setting.UnifiedAlertingSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewNotificationPolicyService(am AMConfigStore, prov ProvisioningStore,
|
func NewNotificationPolicyService(am alertmanagerConfigStore, prov ProvisioningStore,
|
||||||
xact TransactionManager, settings setting.UnifiedAlertingSettings, log log.Logger) *NotificationPolicyService {
|
xact TransactionManager, settings setting.UnifiedAlertingSettings, log log.Logger) *NotificationPolicyService {
|
||||||
return &NotificationPolicyService{
|
return &NotificationPolicyService{
|
||||||
configStore: &alertmanagerConfigStoreImpl{store: am},
|
configStore: am,
|
||||||
provenanceStore: prov,
|
provenanceStore: prov,
|
||||||
xact: xact,
|
xact: xact,
|
||||||
log: log,
|
log: log,
|
||||||
@ -29,26 +30,22 @@ func NewNotificationPolicyService(am AMConfigStore, prov ProvisioningStore,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (nps *NotificationPolicyService) GetAMConfigStore() AMConfigStore {
|
|
||||||
return nps.configStore.store
|
|
||||||
}
|
|
||||||
|
|
||||||
func (nps *NotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
func (nps *NotificationPolicyService) GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||||
rev, err := nps.configStore.Get(ctx, orgID)
|
rev, err := nps.configStore.Get(ctx, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return definitions.Route{}, err
|
return definitions.Route{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if rev.cfg.AlertmanagerConfig.Config.Route == nil {
|
if rev.Config.AlertmanagerConfig.Config.Route == nil {
|
||||||
return definitions.Route{}, fmt.Errorf("no route present in current alertmanager config")
|
return definitions.Route{}, fmt.Errorf("no route present in current alertmanager config")
|
||||||
}
|
}
|
||||||
|
|
||||||
provenance, err := nps.provenanceStore.GetProvenance(ctx, rev.cfg.AlertmanagerConfig.Route, orgID)
|
provenance, err := nps.provenanceStore.GetProvenance(ctx, rev.Config.AlertmanagerConfig.Route, orgID)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return definitions.Route{}, err
|
return definitions.Route{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
result := *rev.cfg.AlertmanagerConfig.Route
|
result := *rev.Config.AlertmanagerConfig.Route
|
||||||
result.Provenance = definitions.Provenance(provenance)
|
result.Provenance = definitions.Provenance(provenance)
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
@ -65,7 +62,7 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
receivers, err := nps.receiversToMap(revision.cfg.AlertmanagerConfig.Receivers)
|
receivers, err := nps.receiversToMap(revision.Config.AlertmanagerConfig.Receivers)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@ -77,7 +74,7 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI
|
|||||||
}
|
}
|
||||||
|
|
||||||
muteTimes := map[string]struct{}{}
|
muteTimes := map[string]struct{}{}
|
||||||
for _, mt := range revision.cfg.AlertmanagerConfig.MuteTimeIntervals {
|
for _, mt := range revision.Config.AlertmanagerConfig.MuteTimeIntervals {
|
||||||
muteTimes[mt.Name] = struct{}{}
|
muteTimes[mt.Name] = struct{}{}
|
||||||
}
|
}
|
||||||
err = tree.ValidateMuteTimes(muteTimes)
|
err = tree.ValidateMuteTimes(muteTimes)
|
||||||
@ -85,7 +82,7 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI
|
|||||||
return fmt.Errorf("%w: %s", ErrValidation, err.Error())
|
return fmt.Errorf("%w: %s", ErrValidation, err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
revision.cfg.AlertmanagerConfig.Config.Route = &tree
|
revision.Config.AlertmanagerConfig.Config.Route = &tree
|
||||||
|
|
||||||
return nps.xact.InTransaction(ctx, func(ctx context.Context) error {
|
return nps.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||||
if err := nps.configStore.Save(ctx, revision, orgID); err != nil {
|
if err := nps.configStore.Save(ctx, revision, orgID); err != nil {
|
||||||
@ -96,7 +93,7 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, error) {
|
||||||
defaultCfg, err := deserializeAlertmanagerConfig([]byte(nps.settings.DefaultConfiguration))
|
defaultCfg, err := legacy_storage.DeserializeAlertmanagerConfig([]byte(nps.settings.DefaultConfiguration))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
nps.log.Error("Failed to parse default alertmanager config: %w", err)
|
nps.log.Error("Failed to parse default alertmanager config: %w", err)
|
||||||
return definitions.Route{}, fmt.Errorf("failed to parse default alertmanager config: %w", err)
|
return definitions.Route{}, fmt.Errorf("failed to parse default alertmanager config: %w", err)
|
||||||
@ -107,8 +104,8 @@ func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return definitions.Route{}, err
|
return definitions.Route{}, err
|
||||||
}
|
}
|
||||||
revision.cfg.AlertmanagerConfig.Config.Route = route
|
revision.Config.AlertmanagerConfig.Config.Route = route
|
||||||
err = nps.ensureDefaultReceiverExists(revision.cfg, defaultCfg)
|
err = nps.ensureDefaultReceiverExists(revision.Config, defaultCfg)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return definitions.Route{}, err
|
return definitions.Route{}, err
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
"github.com/grafana/grafana/pkg/services/ngalert/tests/fakes"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
@ -29,7 +30,8 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("error if referenced mute time interval is not existing", func(t *testing.T) {
|
t.Run("error if referenced mute time interval is not existing", func(t *testing.T) {
|
||||||
sut := createNotificationPolicyServiceSut()
|
sut := createNotificationPolicyServiceSut()
|
||||||
sut.configStore.store = &MockAMConfigStore{}
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut.configStore = legacy_storage.NewAlertmanagerConfigStore(mockStore)
|
||||||
cfg := createTestAlertingConfig()
|
cfg := createTestAlertingConfig()
|
||||||
cfg.AlertmanagerConfig.MuteTimeIntervals = []config.MuteTimeInterval{
|
cfg.AlertmanagerConfig.MuteTimeIntervals = []config.MuteTimeInterval{
|
||||||
{
|
{
|
||||||
@ -37,10 +39,10 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
TimeIntervals: []timeinterval.TimeInterval{},
|
TimeIntervals: []timeinterval.TimeInterval{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
data, _ := serializeAlertmanagerConfig(*cfg)
|
data, _ := legacy_storage.SerializeAlertmanagerConfig(*cfg)
|
||||||
sut.configStore.store.(*MockAMConfigStore).On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
|
mockStore.On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
|
||||||
Return(&models.AlertConfiguration{AlertmanagerConfiguration: string(data)}, nil)
|
Return(&models.AlertConfiguration{AlertmanagerConfiguration: string(data)}, nil)
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil)
|
Return(nil)
|
||||||
newRoute := createTestRoutingTree()
|
newRoute := createTestRoutingTree()
|
||||||
@ -55,7 +57,8 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("pass if referenced mute time interval is existing", func(t *testing.T) {
|
t.Run("pass if referenced mute time interval is existing", func(t *testing.T) {
|
||||||
sut := createNotificationPolicyServiceSut()
|
sut := createNotificationPolicyServiceSut()
|
||||||
sut.configStore.store = &MockAMConfigStore{}
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut.configStore = legacy_storage.NewAlertmanagerConfigStore(mockStore)
|
||||||
cfg := createTestAlertingConfig()
|
cfg := createTestAlertingConfig()
|
||||||
cfg.AlertmanagerConfig.MuteTimeIntervals = []config.MuteTimeInterval{
|
cfg.AlertmanagerConfig.MuteTimeIntervals = []config.MuteTimeInterval{
|
||||||
{
|
{
|
||||||
@ -63,10 +66,10 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
TimeIntervals: []timeinterval.TimeInterval{},
|
TimeIntervals: []timeinterval.TimeInterval{},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
data, _ := serializeAlertmanagerConfig(*cfg)
|
data, _ := legacy_storage.SerializeAlertmanagerConfig(*cfg)
|
||||||
sut.configStore.store.(*MockAMConfigStore).On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
|
mockStore.On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
|
||||||
Return(&models.AlertConfiguration{AlertmanagerConfiguration: string(data)}, nil)
|
Return(&models.AlertConfiguration{AlertmanagerConfiguration: string(data)}, nil)
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil)
|
Return(nil)
|
||||||
newRoute := createTestRoutingTree()
|
newRoute := createTestRoutingTree()
|
||||||
@ -131,12 +134,13 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("existing receiver reference will pass", func(t *testing.T) {
|
t.Run("existing receiver reference will pass", func(t *testing.T) {
|
||||||
sut := createNotificationPolicyServiceSut()
|
sut := createNotificationPolicyServiceSut()
|
||||||
sut.configStore.store = &MockAMConfigStore{}
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut.configStore = legacy_storage.NewAlertmanagerConfigStore(mockStore)
|
||||||
cfg := createTestAlertingConfig()
|
cfg := createTestAlertingConfig()
|
||||||
data, _ := serializeAlertmanagerConfig(*cfg)
|
data, _ := legacy_storage.SerializeAlertmanagerConfig(*cfg)
|
||||||
sut.configStore.store.(*MockAMConfigStore).On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
|
mockStore.On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
|
||||||
Return(&models.AlertConfiguration{AlertmanagerConfiguration: string(data)}, nil)
|
Return(&models.AlertConfiguration{AlertmanagerConfiguration: string(data)}, nil)
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil)
|
Return(nil)
|
||||||
newRoute := createTestRoutingTree()
|
newRoute := createTestRoutingTree()
|
||||||
@ -171,15 +175,16 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("service respects concurrency token when updating", func(t *testing.T) {
|
t.Run("service respects concurrency token when updating", func(t *testing.T) {
|
||||||
sut := createNotificationPolicyServiceSut()
|
sut := createNotificationPolicyServiceSut()
|
||||||
|
fake := fakes.NewFakeAlertmanagerConfigStore(defaultAlertmanagerConfigJSON)
|
||||||
|
sut.configStore = legacy_storage.NewAlertmanagerConfigStore(fake)
|
||||||
newRoute := createTestRoutingTree()
|
newRoute := createTestRoutingTree()
|
||||||
config, err := sut.GetAMConfigStore().GetLatestAlertmanagerConfiguration(context.Background(), 1)
|
config, err := sut.configStore.Get(context.Background(), 1)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
expectedConcurrencyToken := config.ConfigurationHash
|
expectedConcurrencyToken := config.ConcurrencyToken
|
||||||
|
|
||||||
err = sut.UpdatePolicyTree(context.Background(), 1, newRoute, models.ProvenanceAPI)
|
err = sut.UpdatePolicyTree(context.Background(), 1, newRoute, models.ProvenanceAPI)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
fake := sut.GetAMConfigStore().(*fakes.FakeAlertmanagerConfigStore)
|
|
||||||
intercepted := fake.LastSaveCommand
|
intercepted := fake.LastSaveCommand
|
||||||
require.Equal(t, expectedConcurrencyToken, intercepted.FetchedConfigurationHash)
|
require.Equal(t, expectedConcurrencyToken, intercepted.FetchedConfigurationHash)
|
||||||
})
|
})
|
||||||
@ -209,7 +214,8 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("deleting route with missing default receiver restores receiver", func(t *testing.T) {
|
t.Run("deleting route with missing default receiver restores receiver", func(t *testing.T) {
|
||||||
sut := createNotificationPolicyServiceSut()
|
sut := createNotificationPolicyServiceSut()
|
||||||
sut.configStore.store = &MockAMConfigStore{}
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut.configStore = legacy_storage.NewAlertmanagerConfigStore(mockStore)
|
||||||
cfg := createTestAlertingConfig()
|
cfg := createTestAlertingConfig()
|
||||||
cfg.AlertmanagerConfig.Route = &definitions.Route{
|
cfg.AlertmanagerConfig.Route = &definitions.Route{
|
||||||
Receiver: "slack receiver",
|
Receiver: "slack receiver",
|
||||||
@ -222,11 +228,11 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
},
|
},
|
||||||
// No default receiver! Only our custom one.
|
// No default receiver! Only our custom one.
|
||||||
}
|
}
|
||||||
data, _ := serializeAlertmanagerConfig(*cfg)
|
data, _ := legacy_storage.SerializeAlertmanagerConfig(*cfg)
|
||||||
sut.configStore.store.(*MockAMConfigStore).On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
|
mockStore.On("GetLatestAlertmanagerConfiguration", mock.Anything, mock.Anything).
|
||||||
Return(&models.AlertConfiguration{AlertmanagerConfiguration: string(data)}, nil)
|
Return(&models.AlertConfiguration{AlertmanagerConfiguration: string(data)}, nil)
|
||||||
var interceptedSave = models.SaveAlertmanagerConfigurationCmd{}
|
var interceptedSave = models.SaveAlertmanagerConfigurationCmd{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceedsIntercept(&interceptedSave)
|
mockStore.EXPECT().SaveSucceedsIntercept(&interceptedSave)
|
||||||
|
|
||||||
tree, err := sut.ResetPolicyTree(context.Background(), 1)
|
tree, err := sut.ResetPolicyTree(context.Background(), 1)
|
||||||
|
|
||||||
@ -234,7 +240,7 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
require.Equal(t, "grafana-default-email", tree.Receiver)
|
require.Equal(t, "grafana-default-email", tree.Receiver)
|
||||||
require.NotEmpty(t, interceptedSave.AlertmanagerConfiguration)
|
require.NotEmpty(t, interceptedSave.AlertmanagerConfiguration)
|
||||||
// Deserializing with no error asserts that the saved configStore is semantically valid.
|
// Deserializing with no error asserts that the saved configStore is semantically valid.
|
||||||
newCfg, err := deserializeAlertmanagerConfig([]byte(interceptedSave.AlertmanagerConfiguration))
|
newCfg, err := legacy_storage.DeserializeAlertmanagerConfig([]byte(interceptedSave.AlertmanagerConfiguration))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Len(t, newCfg.AlertmanagerConfig.Receivers, 2)
|
require.Len(t, newCfg.AlertmanagerConfig.Receivers, 2)
|
||||||
})
|
})
|
||||||
@ -242,7 +248,7 @@ func TestNotificationPolicyService(t *testing.T) {
|
|||||||
|
|
||||||
func createNotificationPolicyServiceSut() *NotificationPolicyService {
|
func createNotificationPolicyServiceSut() *NotificationPolicyService {
|
||||||
return &NotificationPolicyService{
|
return &NotificationPolicyService{
|
||||||
configStore: &alertmanagerConfigStoreImpl{store: fakes.NewFakeAlertmanagerConfigStore(defaultAlertmanagerConfigJSON)},
|
configStore: legacy_storage.NewAlertmanagerConfigStore(fakes.NewFakeAlertmanagerConfigStore(defaultAlertmanagerConfigJSON)),
|
||||||
provenanceStore: fakes.NewFakeProvisioningStore(),
|
provenanceStore: fakes.NewFakeProvisioningStore(),
|
||||||
xact: newNopTransactionManager(),
|
xact: newNopTransactionManager(),
|
||||||
log: log.NewNopLogger(),
|
log: log.NewNopLogger(),
|
||||||
@ -259,7 +265,7 @@ func createTestRoutingTree() definitions.Route {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func createTestAlertingConfig() *definitions.PostableUserConfig {
|
func createTestAlertingConfig() *definitions.PostableUserConfig {
|
||||||
cfg, _ := deserializeAlertmanagerConfig([]byte(defaultConfig))
|
cfg, _ := legacy_storage.DeserializeAlertmanagerConfig([]byte(defaultConfig))
|
||||||
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers,
|
cfg.AlertmanagerConfig.Receivers = append(cfg.AlertmanagerConfig.Receivers,
|
||||||
&definitions.PostableApiReceiver{
|
&definitions.PostableApiReceiver{
|
||||||
Receiver: config.Receiver{
|
Receiver: config.Receiver{
|
||||||
|
@ -2,20 +2,15 @@ package provisioning
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
|
||||||
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/quota"
|
"github.com/grafana/grafana/pkg/services/quota"
|
||||||
)
|
)
|
||||||
|
|
||||||
// AMStore is a store of Alertmanager configurations.
|
type alertmanagerConfigStore interface {
|
||||||
//
|
Get(ctx context.Context, orgID int64) (*legacy_storage.ConfigRevision, error)
|
||||||
//go:generate mockery --name AMConfigStore --structname MockAMConfigStore --inpackage --filename persist_mock.go --with-expecter
|
Save(ctx context.Context, revision *legacy_storage.ConfigRevision, orgID int64) error
|
||||||
type AMConfigStore interface {
|
|
||||||
GetLatestAlertmanagerConfiguration(ctx context.Context, orgID int64) (*models.AlertConfiguration, error)
|
|
||||||
UpdateAlertmanagerConfiguration(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// ProvisioningStore is a store of provisioning data for arbitrary objects.
|
// ProvisioningStore is a store of provisioning data for arbitrary objects.
|
||||||
@ -50,12 +45,3 @@ type RuleStore interface {
|
|||||||
type QuotaChecker interface {
|
type QuotaChecker interface {
|
||||||
CheckQuotaReached(ctx context.Context, target quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error)
|
CheckQuotaReached(ctx context.Context, target quota.TargetSrv, scopeParams *quota.ScopeParameters) (bool, error)
|
||||||
}
|
}
|
||||||
|
|
||||||
// PersistConfig validates to config before eventually persisting it if no error occurs
|
|
||||||
func PersistConfig(ctx context.Context, store AMConfigStore, cmd *models.SaveAlertmanagerConfigurationCmd) error {
|
|
||||||
cfg := &definitions.PostableUserConfig{}
|
|
||||||
if err := json.Unmarshal([]byte(cmd.AlertmanagerConfiguration), cfg); err != nil {
|
|
||||||
return fmt.Errorf("change would result in an invalid configuration state: %w", err)
|
|
||||||
}
|
|
||||||
return store.UpdateAlertmanagerConfiguration(ctx, cmd)
|
|
||||||
}
|
|
||||||
|
@ -10,15 +10,15 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type TemplateService struct {
|
type TemplateService struct {
|
||||||
configStore *alertmanagerConfigStoreImpl
|
configStore alertmanagerConfigStore
|
||||||
provenanceStore ProvisioningStore
|
provenanceStore ProvisioningStore
|
||||||
xact TransactionManager
|
xact TransactionManager
|
||||||
log log.Logger
|
log log.Logger
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewTemplateService(config AMConfigStore, prov ProvisioningStore, xact TransactionManager, log log.Logger) *TemplateService {
|
func NewTemplateService(config alertmanagerConfigStore, prov ProvisioningStore, xact TransactionManager, log log.Logger) *TemplateService {
|
||||||
return &TemplateService{
|
return &TemplateService{
|
||||||
configStore: &alertmanagerConfigStoreImpl{store: config},
|
configStore: config,
|
||||||
provenanceStore: prov,
|
provenanceStore: prov,
|
||||||
xact: xact,
|
xact: xact,
|
||||||
log: log,
|
log: log,
|
||||||
@ -31,8 +31,8 @@ func (t *TemplateService) GetTemplates(ctx context.Context, orgID int64) ([]defi
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
templates := make([]definitions.NotificationTemplate, 0, len(revision.cfg.TemplateFiles))
|
templates := make([]definitions.NotificationTemplate, 0, len(revision.Config.TemplateFiles))
|
||||||
for name, tmpl := range revision.cfg.TemplateFiles {
|
for name, tmpl := range revision.Config.TemplateFiles {
|
||||||
tmpl := definitions.NotificationTemplate{
|
tmpl := definitions.NotificationTemplate{
|
||||||
Name: name,
|
Name: name,
|
||||||
Template: tmpl,
|
Template: tmpl,
|
||||||
@ -61,10 +61,10 @@ func (t *TemplateService) SetTemplate(ctx context.Context, orgID int64, tmpl def
|
|||||||
return definitions.NotificationTemplate{}, err
|
return definitions.NotificationTemplate{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if revision.cfg.TemplateFiles == nil {
|
if revision.Config.TemplateFiles == nil {
|
||||||
revision.cfg.TemplateFiles = map[string]string{}
|
revision.Config.TemplateFiles = map[string]string{}
|
||||||
}
|
}
|
||||||
revision.cfg.TemplateFiles[tmpl.Name] = tmpl.Template
|
revision.Config.TemplateFiles[tmpl.Name] = tmpl.Template
|
||||||
|
|
||||||
err = t.xact.InTransaction(ctx, func(ctx context.Context) error {
|
err = t.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||||
if err := t.configStore.Save(ctx, revision, orgID); err != nil {
|
if err := t.configStore.Save(ctx, revision, orgID); err != nil {
|
||||||
@ -85,7 +85,7 @@ func (t *TemplateService) DeleteTemplate(ctx context.Context, orgID int64, name
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
delete(revision.cfg.TemplateFiles, name)
|
delete(revision.Config.TemplateFiles, name)
|
||||||
|
|
||||||
return t.xact.InTransaction(ctx, func(ctx context.Context) error {
|
return t.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||||
if err := t.configStore.Save(ctx, revision, orgID); err != nil {
|
if err := t.configStore.Save(ctx, revision, orgID); err != nil {
|
||||||
|
@ -11,13 +11,15 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/infra/log"
|
"github.com/grafana/grafana/pkg/infra/log"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestTemplateService(t *testing.T) {
|
func TestTemplateService(t *testing.T) {
|
||||||
t.Run("service returns templates from config file", func(t *testing.T) {
|
t.Run("service returns templates from config file", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: configWithTemplates,
|
AlertmanagerConfiguration: configWithTemplates,
|
||||||
})
|
})
|
||||||
@ -30,8 +32,9 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("service returns empty map when config file contains no templates", func(t *testing.T) {
|
t.Run("service returns empty map when config file contains no templates", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: defaultConfig,
|
AlertmanagerConfiguration: defaultConfig,
|
||||||
})
|
})
|
||||||
@ -44,8 +47,9 @@ func TestTemplateService(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("service propagates errors", func(t *testing.T) {
|
t.Run("service propagates errors", func(t *testing.T) {
|
||||||
t.Run("when unable to read config", func(t *testing.T) {
|
t.Run("when unable to read config", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil, fmt.Errorf("failed"))
|
Return(nil, fmt.Errorf("failed"))
|
||||||
|
|
||||||
@ -55,32 +59,35 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when config is invalid", func(t *testing.T) {
|
t.Run("when config is invalid", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: brokenConfig,
|
AlertmanagerConfiguration: brokenConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err := sut.GetTemplates(context.Background(), 1)
|
_, err := sut.GetTemplates(context.Background(), 1)
|
||||||
|
|
||||||
require.Truef(t, ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error())
|
require.Truef(t, legacy_storage.ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when no AM config in current org", func(t *testing.T) {
|
t.Run("when no AM config in current org", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil, nil)
|
Return(nil, nil)
|
||||||
|
|
||||||
_, err := sut.GetTemplates(context.Background(), 1)
|
_, err := sut.GetTemplates(context.Background(), 1)
|
||||||
|
|
||||||
require.Truef(t, ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error())
|
require.Truef(t, legacy_storage.ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error())
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("setting templates", func(t *testing.T) {
|
t.Run("setting templates", func(t *testing.T) {
|
||||||
t.Run("rejects templates that fail validation", func(t *testing.T) {
|
t.Run("rejects templates that fail validation", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := definitions.NotificationTemplate{
|
tmpl := definitions.NotificationTemplate{
|
||||||
Name: "",
|
Name: "",
|
||||||
Template: "",
|
Template: "",
|
||||||
@ -93,9 +100,10 @@ func TestTemplateService(t *testing.T) {
|
|||||||
|
|
||||||
t.Run("propagates errors", func(t *testing.T) {
|
t.Run("propagates errors", func(t *testing.T) {
|
||||||
t.Run("when unable to read config", func(t *testing.T) {
|
t.Run("when unable to read config", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := createNotificationTemplate()
|
tmpl := createNotificationTemplate()
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil, fmt.Errorf("failed"))
|
Return(nil, fmt.Errorf("failed"))
|
||||||
|
|
||||||
@ -105,38 +113,41 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when config is invalid", func(t *testing.T) {
|
t.Run("when config is invalid", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := createNotificationTemplate()
|
tmpl := createNotificationTemplate()
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: brokenConfig,
|
AlertmanagerConfiguration: brokenConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
||||||
|
|
||||||
require.Truef(t, ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error())
|
require.Truef(t, legacy_storage.ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when no AM config in current org", func(t *testing.T) {
|
t.Run("when no AM config in current org", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := createNotificationTemplate()
|
tmpl := createNotificationTemplate()
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil, nil)
|
Return(nil, nil)
|
||||||
|
|
||||||
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
||||||
|
|
||||||
require.Truef(t, ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error())
|
require.Truef(t, legacy_storage.ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when provenance fails to save", func(t *testing.T) {
|
t.Run("when provenance fails to save", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := createNotificationTemplate()
|
tmpl := createNotificationTemplate()
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: configWithTemplates,
|
AlertmanagerConfiguration: configWithTemplates,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
||||||
SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
SetProvenance(mock.Anything, mock.Anything, mock.Anything, mock.Anything).
|
||||||
Return(fmt.Errorf("failed to save provenance"))
|
Return(fmt.Errorf("failed to save provenance"))
|
||||||
@ -147,13 +158,14 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when AM config fails to save", func(t *testing.T) {
|
t.Run("when AM config fails to save", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := createNotificationTemplate()
|
tmpl := createNotificationTemplate()
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: configWithTemplates,
|
AlertmanagerConfiguration: configWithTemplates,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(fmt.Errorf("failed to save config"))
|
Return(fmt.Errorf("failed to save config"))
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
@ -165,13 +177,14 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("adds new template to config file on success", func(t *testing.T) {
|
t.Run("adds new template to config file on success", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := createNotificationTemplate()
|
tmpl := createNotificationTemplate()
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: configWithTemplates,
|
AlertmanagerConfiguration: configWithTemplates,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
||||||
@ -180,13 +193,14 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("succeeds when stitching config file with no templates", func(t *testing.T) {
|
t.Run("succeeds when stitching config file with no templates", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := createNotificationTemplate()
|
tmpl := createNotificationTemplate()
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: defaultConfig,
|
AlertmanagerConfiguration: defaultConfig,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
||||||
@ -195,16 +209,17 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("normalizes template content with no define", func(t *testing.T) {
|
t.Run("normalizes template content with no define", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := definitions.NotificationTemplate{
|
tmpl := definitions.NotificationTemplate{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Template: "content",
|
Template: "content",
|
||||||
}
|
}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: defaultConfig,
|
AlertmanagerConfiguration: defaultConfig,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
result, _ := sut.SetTemplate(context.Background(), 1, tmpl)
|
result, _ := sut.SetTemplate(context.Background(), 1, tmpl)
|
||||||
@ -214,16 +229,17 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("avoids normalizing template content with define", func(t *testing.T) {
|
t.Run("avoids normalizing template content with define", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := definitions.NotificationTemplate{
|
tmpl := definitions.NotificationTemplate{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Template: "{{define \"name\"}}content{{end}}",
|
Template: "{{define \"name\"}}content{{end}}",
|
||||||
}
|
}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: defaultConfig,
|
AlertmanagerConfiguration: defaultConfig,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
result, _ := sut.SetTemplate(context.Background(), 1, tmpl)
|
result, _ := sut.SetTemplate(context.Background(), 1, tmpl)
|
||||||
@ -232,16 +248,17 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("rejects syntactically invalid template", func(t *testing.T) {
|
t.Run("rejects syntactically invalid template", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := definitions.NotificationTemplate{
|
tmpl := definitions.NotificationTemplate{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Template: "{{ .MyField }",
|
Template: "{{ .MyField }",
|
||||||
}
|
}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: defaultConfig,
|
AlertmanagerConfiguration: defaultConfig,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
||||||
@ -250,16 +267,17 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("does not reject template with unknown field", func(t *testing.T) {
|
t.Run("does not reject template with unknown field", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
tmpl := definitions.NotificationTemplate{
|
tmpl := definitions.NotificationTemplate{
|
||||||
Name: "name",
|
Name: "name",
|
||||||
Template: "{{ .NotAField }}",
|
Template: "{{ .NotAField }}",
|
||||||
}
|
}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: defaultConfig,
|
AlertmanagerConfiguration: defaultConfig,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
_, err := sut.SetTemplate(context.Background(), 1, tmpl)
|
||||||
@ -271,8 +289,9 @@ func TestTemplateService(t *testing.T) {
|
|||||||
t.Run("deleting templates", func(t *testing.T) {
|
t.Run("deleting templates", func(t *testing.T) {
|
||||||
t.Run("propagates errors", func(t *testing.T) {
|
t.Run("propagates errors", func(t *testing.T) {
|
||||||
t.Run("when unable to read config", func(t *testing.T) {
|
t.Run("when unable to read config", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil, fmt.Errorf("failed"))
|
Return(nil, fmt.Errorf("failed"))
|
||||||
|
|
||||||
@ -282,35 +301,38 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when config is invalid", func(t *testing.T) {
|
t.Run("when config is invalid", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: brokenConfig,
|
AlertmanagerConfiguration: brokenConfig,
|
||||||
})
|
})
|
||||||
|
|
||||||
err := sut.DeleteTemplate(context.Background(), 1, "template")
|
err := sut.DeleteTemplate(context.Background(), 1, "template")
|
||||||
|
|
||||||
require.Truef(t, ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error())
|
require.Truef(t, legacy_storage.ErrBadAlertmanagerConfiguration.Base.Is(err), "expected ErrBadAlertmanagerConfiguration but got %s", err.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when no AM config in current org", func(t *testing.T) {
|
t.Run("when no AM config in current org", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(nil, nil)
|
Return(nil, nil)
|
||||||
|
|
||||||
err := sut.DeleteTemplate(context.Background(), 1, "template")
|
err := sut.DeleteTemplate(context.Background(), 1, "template")
|
||||||
|
|
||||||
require.Truef(t, ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error())
|
require.Truef(t, legacy_storage.ErrNoAlertmanagerConfiguration.Is(err), "expected ErrNoAlertmanagerConfiguration but got %s", err.Error())
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when provenance fails to save", func(t *testing.T) {
|
t.Run("when provenance fails to save", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: configWithTemplates,
|
AlertmanagerConfiguration: configWithTemplates,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().
|
||||||
DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).
|
DeleteProvenance(mock.Anything, mock.Anything, mock.Anything).
|
||||||
Return(fmt.Errorf("failed to save provenance"))
|
Return(fmt.Errorf("failed to save provenance"))
|
||||||
@ -321,12 +343,13 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("when AM config fails to save", func(t *testing.T) {
|
t.Run("when AM config fails to save", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: configWithTemplates,
|
AlertmanagerConfiguration: configWithTemplates,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
mockStore.EXPECT().
|
||||||
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
||||||
Return(fmt.Errorf("failed to save config"))
|
Return(fmt.Errorf("failed to save config"))
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
@ -338,12 +361,13 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("deletes template from config file on success", func(t *testing.T) {
|
t.Run("deletes template from config file on success", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: configWithTemplates,
|
AlertmanagerConfiguration: configWithTemplates,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
err := sut.DeleteTemplate(context.Background(), 1, "a")
|
err := sut.DeleteTemplate(context.Background(), 1, "a")
|
||||||
@ -352,12 +376,13 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("does not error when deleting templates that do not exist", func(t *testing.T) {
|
t.Run("does not error when deleting templates that do not exist", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: configWithTemplates,
|
AlertmanagerConfiguration: configWithTemplates,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
err := sut.DeleteTemplate(context.Background(), 1, "does not exist")
|
err := sut.DeleteTemplate(context.Background(), 1, "does not exist")
|
||||||
@ -366,12 +391,13 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
|
|
||||||
t.Run("succeeds when deleting from config file with no template section", func(t *testing.T) {
|
t.Run("succeeds when deleting from config file with no template section", func(t *testing.T) {
|
||||||
sut := createTemplateServiceSut()
|
mockStore := &legacy_storage.MockAMConfigStore{}
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().
|
sut := createTemplateServiceSut(legacy_storage.NewAlertmanagerConfigStore(mockStore))
|
||||||
|
mockStore.EXPECT().
|
||||||
GetsConfig(models.AlertConfiguration{
|
GetsConfig(models.AlertConfiguration{
|
||||||
AlertmanagerConfiguration: defaultConfig,
|
AlertmanagerConfiguration: defaultConfig,
|
||||||
})
|
})
|
||||||
sut.configStore.store.(*MockAMConfigStore).EXPECT().SaveSucceeds()
|
mockStore.EXPECT().SaveSucceeds()
|
||||||
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
sut.provenanceStore.(*MockProvisioningStore).EXPECT().SaveSucceeds()
|
||||||
|
|
||||||
err := sut.DeleteTemplate(context.Background(), 1, "a")
|
err := sut.DeleteTemplate(context.Background(), 1, "a")
|
||||||
@ -381,9 +407,9 @@ func TestTemplateService(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func createTemplateServiceSut() *TemplateService {
|
func createTemplateServiceSut(configStore alertmanagerConfigStore) *TemplateService {
|
||||||
return &TemplateService{
|
return &TemplateService{
|
||||||
configStore: &alertmanagerConfigStoreImpl{store: &MockAMConfigStore{}},
|
configStore: configStore,
|
||||||
provenanceStore: &MockProvisioningStore{},
|
provenanceStore: &MockProvisioningStore{},
|
||||||
xact: newNopTransactionManager(),
|
xact: newNopTransactionManager(),
|
||||||
log: log.NewNopLogger(),
|
log: log.NewNopLogger(),
|
||||||
|
@ -70,25 +70,6 @@ func (n *NopTransactionManager) InTransaction(ctx context.Context, work func(ctx
|
|||||||
return work(context.WithValue(ctx, NopTransactionManager{}, struct{}{}))
|
return work(context.WithValue(ctx, NopTransactionManager{}, struct{}{}))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MockAMConfigStore_Expecter) GetsConfig(ac models.AlertConfiguration) *MockAMConfigStore_Expecter {
|
|
||||||
m.GetLatestAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(&ac, nil)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAMConfigStore_Expecter) SaveSucceeds() *MockAMConfigStore_Expecter {
|
|
||||||
m.UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).Return(nil)
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockAMConfigStore_Expecter) SaveSucceedsIntercept(intercepted *models.SaveAlertmanagerConfigurationCmd) *MockAMConfigStore_Expecter {
|
|
||||||
m.UpdateAlertmanagerConfiguration(mock.Anything, mock.Anything).
|
|
||||||
Return(nil).
|
|
||||||
Run(func(ctx context.Context, cmd *models.SaveAlertmanagerConfigurationCmd) {
|
|
||||||
*intercepted = *cmd
|
|
||||||
})
|
|
||||||
return m
|
|
||||||
}
|
|
||||||
|
|
||||||
func (m *MockProvisioningStore_Expecter) GetReturns(p models.Provenance) *MockProvisioningStore_Expecter {
|
func (m *MockProvisioningStore_Expecter) GetReturns(p models.Provenance) *MockProvisioningStore_Expecter {
|
||||||
m.GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(p, nil)
|
m.GetProvenance(mock.Anything, mock.Anything, mock.Anything).Return(p, nil)
|
||||||
m.GetProvenances(mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
m.GetProvenances(mock.Anything, mock.Anything, mock.Anything).Return(nil, nil)
|
||||||
@ -111,39 +92,6 @@ func (m *MockQuotaChecker_Expecter) LimitExceeded() *MockQuotaChecker_Expecter {
|
|||||||
return m
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
type methodCall struct {
|
|
||||||
Method string
|
|
||||||
Args []interface{}
|
|
||||||
}
|
|
||||||
|
|
||||||
type alertmanagerConfigStoreFake struct {
|
|
||||||
Calls []methodCall
|
|
||||||
GetFn func(ctx context.Context, orgID int64) (*cfgRevision, error)
|
|
||||||
SaveFn func(ctx context.Context, revision *cfgRevision) error
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *alertmanagerConfigStoreFake) Get(ctx context.Context, orgID int64) (*cfgRevision, error) {
|
|
||||||
a.Calls = append(a.Calls, methodCall{
|
|
||||||
Method: "Get",
|
|
||||||
Args: []interface{}{ctx, orgID},
|
|
||||||
})
|
|
||||||
if a.GetFn != nil {
|
|
||||||
return a.GetFn(ctx, orgID)
|
|
||||||
}
|
|
||||||
return nil, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
func (a *alertmanagerConfigStoreFake) Save(ctx context.Context, revision *cfgRevision, orgID int64) error {
|
|
||||||
a.Calls = append(a.Calls, methodCall{
|
|
||||||
Method: "Save",
|
|
||||||
Args: []interface{}{ctx, revision, orgID},
|
|
||||||
})
|
|
||||||
if a.SaveFn != nil {
|
|
||||||
return a.SaveFn(ctx, revision)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
type NotificationSettingsValidatorProviderFake struct {
|
type NotificationSettingsValidatorProviderFake struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/services/folder"
|
"github.com/grafana/grafana/pkg/services/folder"
|
||||||
alertingauthz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
alertingauthz "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
|
||||||
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
"github.com/grafana/grafana/pkg/services/ngalert/provisioning"
|
||||||
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
||||||
"github.com/grafana/grafana/pkg/services/notifications"
|
"github.com/grafana/grafana/pkg/services/notifications"
|
||||||
@ -270,13 +271,21 @@ func (ps *ProvisioningServiceImpl) ProvisionAlerting(ctx context.Context) error
|
|||||||
notifier.NewCachedNotificationSettingsValidationService(&st),
|
notifier.NewCachedNotificationSettingsValidationService(&st),
|
||||||
alertingauthz.NewRuleService(ps.ac),
|
alertingauthz.NewRuleService(ps.ac),
|
||||||
)
|
)
|
||||||
receiverSvc := notifier.NewReceiverService(ps.ac, &st, st, ps.secretService, ps.SQLStore, ps.log)
|
configStore := legacy_storage.NewAlertmanagerConfigStore(&st)
|
||||||
contactPointService := provisioning.NewContactPointService(&st, ps.secretService,
|
receiverSvc := notifier.NewReceiverService(
|
||||||
|
ps.ac,
|
||||||
|
configStore,
|
||||||
|
st,
|
||||||
|
ps.secretService,
|
||||||
|
ps.SQLStore,
|
||||||
|
ps.log,
|
||||||
|
)
|
||||||
|
contactPointService := provisioning.NewContactPointService(configStore, ps.secretService,
|
||||||
st, ps.SQLStore, receiverSvc, ps.log, &st)
|
st, ps.SQLStore, receiverSvc, ps.log, &st)
|
||||||
notificationPolicyService := provisioning.NewNotificationPolicyService(&st,
|
notificationPolicyService := provisioning.NewNotificationPolicyService(configStore,
|
||||||
st, ps.SQLStore, ps.Cfg.UnifiedAlerting, ps.log)
|
st, ps.SQLStore, ps.Cfg.UnifiedAlerting, ps.log)
|
||||||
mutetimingsService := provisioning.NewMuteTimingService(&st, st, &st, ps.log, &st)
|
mutetimingsService := provisioning.NewMuteTimingService(configStore, st, &st, ps.log, &st)
|
||||||
templateService := provisioning.NewTemplateService(&st, st, &st, ps.log)
|
templateService := provisioning.NewTemplateService(configStore, st, &st, ps.log)
|
||||||
cfg := prov_alerting.ProvisionerConfig{
|
cfg := prov_alerting.ProvisionerConfig{
|
||||||
Path: alertingPath,
|
Path: alertingPath,
|
||||||
RuleService: *ruleService,
|
RuleService: *ruleService,
|
||||||
|
Loading…
Reference in New Issue
Block a user