Alerting: Include in-use metadata in k8s receiver LIST & GET (#93016)
* Include in-use metadata in k8s receiver List & Get
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package v0alpha1
|
||||
|
||||
import "fmt"
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
const InternalPrefix = "grafana.com/"
|
||||
const ProvenanceStatusAnnotationKey = InternalPrefix + "provenance"
|
||||
@@ -60,3 +62,17 @@ func (o *Receiver) SetAccessControl(action string) {
|
||||
func AccessControlAnnotation(action string) string {
|
||||
return fmt.Sprintf("%s%s/%s", InternalPrefix, "access", action)
|
||||
}
|
||||
|
||||
func (o *Receiver) SetInUse(routesCnt int, rules []string) {
|
||||
if o.Annotations == nil {
|
||||
o.Annotations = make(map[string]string, 2)
|
||||
}
|
||||
o.Annotations[InUseAnnotation("routes")] = fmt.Sprintf("%d", routesCnt)
|
||||
o.Annotations[InUseAnnotation("rules")] = fmt.Sprintf("%d", len(rules))
|
||||
}
|
||||
|
||||
// InUseAnnotation returns the key for the in-use annotation for the given resource.
|
||||
// Ex. grafana.com/inUse/routes, grafana.com/inUse/rules.
|
||||
func InUseAnnotation(resource string) string {
|
||||
return fmt.Sprintf("%s%s/%s", InternalPrefix, "inUse", resource)
|
||||
}
|
||||
|
||||
@@ -15,7 +15,14 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||
)
|
||||
|
||||
func convertToK8sResources(orgID int64, receivers []*ngmodels.Receiver, accesses map[string]ngmodels.ReceiverPermissionSet, namespacer request.NamespaceMapper, selector fields.Selector) (*model.ReceiverList, error) {
|
||||
func convertToK8sResources(
|
||||
orgID int64,
|
||||
receivers []*ngmodels.Receiver,
|
||||
accesses map[string]ngmodels.ReceiverPermissionSet,
|
||||
metadatas map[string]ngmodels.ReceiverMetadata,
|
||||
namespacer request.NamespaceMapper,
|
||||
selector fields.Selector,
|
||||
) (*model.ReceiverList, error) {
|
||||
result := &model.ReceiverList{
|
||||
Items: make([]model.Receiver, 0, len(receivers)),
|
||||
}
|
||||
@@ -26,7 +33,13 @@ func convertToK8sResources(orgID int64, receivers []*ngmodels.Receiver, accesses
|
||||
access = &a
|
||||
}
|
||||
}
|
||||
k8sResource, err := convertToK8sResource(orgID, receiver, access, namespacer)
|
||||
var metadata *ngmodels.ReceiverMetadata
|
||||
if metadatas != nil {
|
||||
if m, ok := metadatas[receiver.GetUID()]; ok {
|
||||
metadata = &m
|
||||
}
|
||||
}
|
||||
k8sResource, err := convertToK8sResource(orgID, receiver, access, metadata, namespacer)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
@@ -38,7 +51,13 @@ func convertToK8sResources(orgID int64, receivers []*ngmodels.Receiver, accesses
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func convertToK8sResource(orgID int64, receiver *ngmodels.Receiver, access *ngmodels.ReceiverPermissionSet, namespacer request.NamespaceMapper) (*model.Receiver, error) {
|
||||
func convertToK8sResource(
|
||||
orgID int64,
|
||||
receiver *ngmodels.Receiver,
|
||||
access *ngmodels.ReceiverPermissionSet,
|
||||
metadata *ngmodels.ReceiverMetadata,
|
||||
namespacer request.NamespaceMapper,
|
||||
) (*model.Receiver, error) {
|
||||
spec := model.ReceiverSpec{
|
||||
Title: receiver.Name,
|
||||
}
|
||||
@@ -76,6 +95,14 @@ func convertToK8sResource(orgID int64, receiver *ngmodels.Receiver, access *ngmo
|
||||
}
|
||||
}
|
||||
|
||||
if metadata != nil {
|
||||
rules := make([]string, 0, len(metadata.InUseByRules))
|
||||
for _, rule := range metadata.InUseByRules {
|
||||
rules = append(rules, rule.UID)
|
||||
}
|
||||
r.SetInUse(metadata.InUseByRoutes, rules)
|
||||
}
|
||||
|
||||
return r, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,8 @@ type ReceiverService interface {
|
||||
}
|
||||
|
||||
type MetadataService interface {
|
||||
Access(ctx context.Context, user identity.Requester, receivers ...*ngmodels.Receiver) (map[string]ngmodels.ReceiverPermissionSet, error)
|
||||
AccessControlMetadata(ctx context.Context, user identity.Requester, receivers ...*ngmodels.Receiver) (map[string]ngmodels.ReceiverPermissionSet, error)
|
||||
InUseMetadata(ctx context.Context, orgID int64, receivers ...*ngmodels.Receiver) (map[string]ngmodels.ReceiverMetadata, error)
|
||||
}
|
||||
|
||||
type legacyStorage struct {
|
||||
@@ -90,12 +91,17 @@ func (s *legacyStorage) List(ctx context.Context, opts *internalversion.ListOpti
|
||||
return nil, err
|
||||
}
|
||||
|
||||
accesses, err := s.metadata.Access(ctx, user, res...)
|
||||
accesses, err := s.metadata.AccessControlMetadata(ctx, user, res...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get access control metadata: %w", err)
|
||||
}
|
||||
|
||||
return convertToK8sResources(orgId, res, accesses, s.namespacer, opts.FieldSelector)
|
||||
inUses, err := s.metadata.InUseMetadata(ctx, orgId, res...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get in-use metadata: %w", err)
|
||||
}
|
||||
|
||||
return convertToK8sResources(orgId, res, accesses, inUses, s.namespacer, opts.FieldSelector)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOptions) (runtime.Object, error) {
|
||||
@@ -125,7 +131,7 @@ func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOption
|
||||
}
|
||||
|
||||
var access *ngmodels.ReceiverPermissionSet
|
||||
accesses, err := s.metadata.Access(ctx, user, r)
|
||||
accesses, err := s.metadata.AccessControlMetadata(ctx, user, r)
|
||||
if err == nil {
|
||||
if a, ok := accesses[r.GetUID()]; ok {
|
||||
access = &a
|
||||
@@ -134,7 +140,17 @@ func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOption
|
||||
return nil, fmt.Errorf("failed to get access control metadata: %w", err)
|
||||
}
|
||||
|
||||
return convertToK8sResource(info.OrgID, r, access, s.namespacer)
|
||||
var inUse *ngmodels.ReceiverMetadata
|
||||
inUses, err := s.metadata.InUseMetadata(ctx, info.OrgID, r)
|
||||
if err == nil {
|
||||
if a, ok := inUses[r.GetUID()]; ok {
|
||||
inUse = &a
|
||||
}
|
||||
} else {
|
||||
return nil, fmt.Errorf("failed to get access control metadata: %w", err)
|
||||
}
|
||||
|
||||
return convertToK8sResource(info.OrgID, r, access, inUse, s.namespacer)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Create(ctx context.Context,
|
||||
@@ -172,7 +188,7 @@ func (s *legacyStorage) Create(ctx context.Context,
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertToK8sResource(info.OrgID, out, nil, s.namespacer)
|
||||
return convertToK8sResource(info.OrgID, out, nil, nil, s.namespacer)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Update(ctx context.Context,
|
||||
@@ -224,7 +240,7 @@ func (s *legacyStorage) Update(ctx context.Context,
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
r, err := convertToK8sResource(info.OrgID, updated, nil, s.namespacer)
|
||||
r, err := convertToK8sResource(info.OrgID, updated, nil, nil, s.namespacer)
|
||||
return r, false, err
|
||||
}
|
||||
|
||||
|
||||
@@ -85,7 +85,7 @@ func (t *NotificationsAPIBuilder) GetAPIGroupInfo(
|
||||
return nil, fmt.Errorf("failed to initialize time-interval storage: %w", err)
|
||||
}
|
||||
|
||||
recvStorage, err := receiver.NewStorage(t.ng.Api.ReceiverService, t.namespacer, scheme, optsGetter, dualWriteBuilder, ac.NewReceiverAccess[*ngmodels.Receiver](t.ng.Api.AccessControl, false))
|
||||
recvStorage, err := receiver.NewStorage(t.ng.Api.ReceiverService, t.namespacer, scheme, optsGetter, dualWriteBuilder, t.ng.Api.ReceiverService)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize receiver storage: %w", err)
|
||||
}
|
||||
|
||||
@@ -40,6 +40,12 @@ type ListReceiversQuery struct {
|
||||
Offset int
|
||||
}
|
||||
|
||||
// ReceiverMetadata contains metadata about a receiver's usage in routes and rules.
|
||||
type ReceiverMetadata struct {
|
||||
InUseByRules []AlertRuleKey
|
||||
InUseByRoutes int
|
||||
}
|
||||
|
||||
// Receiver is the domain model representation of a receiver / contact point.
|
||||
type Receiver struct {
|
||||
UID string
|
||||
|
||||
@@ -70,10 +70,18 @@ func (rev *ConfigRevision) UpdateReceiver(receiver *models.Receiver) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReceiverNameUsedByRoutes checks if a receiver name is used in any routes.
|
||||
func (rev *ConfigRevision) ReceiverNameUsedByRoutes(name string) bool {
|
||||
return isReceiverInUse(name, []*definitions.Route{rev.Config.AlertmanagerConfig.Route})
|
||||
}
|
||||
|
||||
// ReceiverUseByName returns a map of receiver names to the number of times they are used in routes.
|
||||
func (rev *ConfigRevision) ReceiverUseByName() map[string]int {
|
||||
m := make(map[string]int)
|
||||
receiverUseCounts([]*definitions.Route{rev.Config.AlertmanagerConfig.Route}, m)
|
||||
return m
|
||||
}
|
||||
|
||||
func (rev *ConfigRevision) GetReceiver(uid string) (*definitions.PostableApiReceiver, error) {
|
||||
for _, r := range rev.Config.AlertmanagerConfig.Receivers {
|
||||
if NameToUid(r.GetName()) == uid {
|
||||
@@ -155,6 +163,17 @@ func isReceiverInUse(name string, routes []*definitions.Route) bool {
|
||||
return false
|
||||
}
|
||||
|
||||
// receiverUseCounts counts how many times receivers are used in a route or any of its sub-routes.
|
||||
func receiverUseCounts(routes []*definitions.Route, m map[string]int) {
|
||||
if len(routes) == 0 {
|
||||
return
|
||||
}
|
||||
for _, route := range routes {
|
||||
m[route.Receiver]++
|
||||
receiverUseCounts(route.Routes, m)
|
||||
}
|
||||
}
|
||||
|
||||
// validateAndSetIntegrationUIDs validates existing integration UIDs and generates them if they are empty.
|
||||
func validateAndSetIntegrationUIDs(receiver *models.Receiver) error {
|
||||
for _, integration := range receiver.Integrations {
|
||||
|
||||
@@ -63,6 +63,8 @@ type receiverAccessControlService interface {
|
||||
AuthorizeCreate(context.Context, identity.Requester) error
|
||||
AuthorizeUpdate(context.Context, identity.Requester, *models.Receiver) error
|
||||
AuthorizeDeleteByUID(context.Context, identity.Requester, string) error
|
||||
|
||||
Access(ctx context.Context, user identity.Requester, receivers ...*models.Receiver) (map[string]models.ReceiverPermissionSet, error)
|
||||
}
|
||||
|
||||
type alertmanagerConfigStore interface {
|
||||
@@ -444,6 +446,48 @@ func (rs *ReceiverService) UsedByRules(ctx context.Context, orgID int64, name st
|
||||
return maps.Keys(keys), nil
|
||||
}
|
||||
|
||||
// AccessControlMetadata returns access control metadata for the given Receivers.
|
||||
func (rs *ReceiverService) AccessControlMetadata(ctx context.Context, user identity.Requester, receivers ...*models.Receiver) (map[string]models.ReceiverPermissionSet, error) {
|
||||
return rs.authz.Access(ctx, user, receivers...)
|
||||
}
|
||||
|
||||
// InUseMetadata returns metadata for the given Receivers about their usage in routes and rules.
|
||||
func (rs *ReceiverService) InUseMetadata(ctx context.Context, orgID int64, receivers ...*models.Receiver) (map[string]models.ReceiverMetadata, error) {
|
||||
revision, err := rs.cfgStore.Get(ctx, orgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
receiverUses := revision.ReceiverUseByName()
|
||||
|
||||
q := models.ListNotificationSettingsQuery{OrgID: orgID}
|
||||
if len(receivers) == 1 {
|
||||
q.ReceiverName = receivers[0].Name
|
||||
}
|
||||
keys, err := rs.ruleNotificationsStore.ListNotificationSettings(ctx, q)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
byReceiver := map[string][]models.AlertRuleKey{}
|
||||
for key, settings := range keys {
|
||||
for _, s := range settings {
|
||||
if s.Receiver != "" {
|
||||
byReceiver[s.Receiver] = append(byReceiver[s.Receiver], key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
results := make(map[string]models.ReceiverMetadata, len(receivers))
|
||||
for _, rcv := range receivers {
|
||||
results[rcv.GetUID()] = models.ReceiverMetadata{
|
||||
InUseByRoutes: receiverUses[rcv.Name],
|
||||
InUseByRules: byReceiver[rcv.Name],
|
||||
}
|
||||
}
|
||||
|
||||
return results, nil
|
||||
}
|
||||
|
||||
func removedIntegrations(old, new *models.Receiver) []*models.Integration {
|
||||
updatedUIDs := make(map[string]struct{}, len(new.Integrations))
|
||||
for _, integration := range new.Integrations {
|
||||
|
||||
@@ -1329,6 +1329,115 @@ func TestReceiverServiceAC_Delete(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestReceiverService_InUseMetadata(t *testing.T) {
|
||||
secretsService := fake_secrets.NewFakeSecretsService()
|
||||
|
||||
admin := &user.SignedInUser{OrgID: 1, OrgRole: org.RoleAdmin, Permissions: map[int64]map[string][]string{
|
||||
1: {
|
||||
accesscontrol.ActionAlertingNotificationsWrite: nil,
|
||||
accesscontrol.ActionAlertingNotificationsRead: nil,
|
||||
},
|
||||
}}
|
||||
|
||||
for _, tc := range []struct {
|
||||
name string
|
||||
user identity.Requester
|
||||
storeRoute definitions.Route
|
||||
storeSettings map[models.AlertRuleKey][]models.NotificationSettings
|
||||
existing []*models.Receiver
|
||||
expectedMetadata map[string]models.ReceiverMetadata
|
||||
}{
|
||||
{
|
||||
name: "mixed metadata",
|
||||
user: admin,
|
||||
existing: []*models.Receiver{
|
||||
util.Pointer(models.ReceiverGen(models.ReceiverMuts.WithName("receiver1"))()),
|
||||
util.Pointer(models.ReceiverGen(models.ReceiverMuts.WithName("receiver2"))()),
|
||||
util.Pointer(models.ReceiverGen(models.ReceiverMuts.WithName("receiver3"))()),
|
||||
util.Pointer(models.ReceiverGen(models.ReceiverMuts.WithName("receiver4"))()),
|
||||
},
|
||||
storeSettings: map[models.AlertRuleKey][]models.NotificationSettings{
|
||||
{OrgID: 1, UID: "rule1uid"}: {
|
||||
models.NotificationSettingsGen(models.NSMuts.WithReceiver("receiver1"))(),
|
||||
models.NotificationSettingsGen(models.NSMuts.WithReceiver("receiver2"))(),
|
||||
},
|
||||
{OrgID: 1, UID: "rule2uid"}: {
|
||||
models.NotificationSettingsGen(models.NSMuts.WithReceiver("receiver2"))(),
|
||||
models.NotificationSettingsGen(models.NSMuts.WithReceiver("receiver3"))(),
|
||||
},
|
||||
},
|
||||
storeRoute: definitions.Route{
|
||||
Receiver: "receiver1",
|
||||
Routes: []*definitions.Route{
|
||||
{Receiver: "receiver2"},
|
||||
{Receiver: "receiver3"},
|
||||
{
|
||||
Receiver: "receiver4",
|
||||
Routes: []*definitions.Route{
|
||||
{Receiver: "receiver1"},
|
||||
{Receiver: "receiver3"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
expectedMetadata: map[string]models.ReceiverMetadata{
|
||||
legacy_storage.NameToUid("receiver1"): {
|
||||
InUseByRules: []models.AlertRuleKey{{OrgID: 1, UID: "rule1uid"}},
|
||||
InUseByRoutes: 2,
|
||||
},
|
||||
legacy_storage.NameToUid("receiver2"): {
|
||||
InUseByRules: []models.AlertRuleKey{{OrgID: 1, UID: "rule1uid"}, {OrgID: 1, UID: "rule2uid"}},
|
||||
InUseByRoutes: 1,
|
||||
},
|
||||
legacy_storage.NameToUid("receiver3"): {
|
||||
InUseByRules: []models.AlertRuleKey{{OrgID: 1, UID: "rule2uid"}},
|
||||
InUseByRoutes: 2,
|
||||
},
|
||||
legacy_storage.NameToUid("receiver4"): {
|
||||
InUseByRules: []models.AlertRuleKey{},
|
||||
InUseByRoutes: 1,
|
||||
},
|
||||
},
|
||||
},
|
||||
} {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
sut := createReceiverServiceSut(t, &secretsService)
|
||||
|
||||
store := sut.ruleNotificationsStore.(*fakeConfigStore)
|
||||
store.notificationSettings = map[int64]map[models.AlertRuleKey][]models.NotificationSettings{
|
||||
1: make(map[models.AlertRuleKey][]models.NotificationSettings),
|
||||
}
|
||||
|
||||
for key, settings := range tc.storeSettings {
|
||||
store.notificationSettings[tc.user.GetOrgID()][key] = settings
|
||||
}
|
||||
|
||||
for _, recv := range tc.existing {
|
||||
_, err := sut.CreateReceiver(context.Background(), recv, tc.user.GetOrgID(), tc.user)
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
// Create route after receivers as they will be referenced.
|
||||
revision, err := sut.cfgStore.Get(context.Background(), tc.user.GetOrgID())
|
||||
require.NoError(t, err)
|
||||
revision.Config.AlertmanagerConfig.Route = &tc.storeRoute
|
||||
err = sut.cfgStore.Save(context.Background(), revision, tc.user.GetOrgID())
|
||||
require.NoError(t, err)
|
||||
|
||||
metadata, err := sut.InUseMetadata(context.Background(), tc.user.GetOrgID(), tc.existing...)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Lenf(t, metadata, len(tc.expectedMetadata), "unexpected metadata length")
|
||||
for _, recv := range tc.existing {
|
||||
expected, ok := tc.expectedMetadata[recv.UID]
|
||||
assert.Truef(t, ok, "missing metadata for receiver uid: %q, name: %q", recv.UID, recv.Name)
|
||||
assert.ElementsMatch(t, expected.InUseByRules, metadata[recv.UID].InUseByRules, "unexpected rules metadata for receiver uid: %q, name: %q", recv.UID, recv.Name)
|
||||
assert.Equalf(t, expected.InUseByRoutes, metadata[recv.UID].InUseByRoutes, "unexpected routes metadata for receiver uid: %q, name: %q", recv.UID, recv.Name)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func createReceiverServiceSut(t *testing.T, encryptSvc secretService) *ReceiverService {
|
||||
cfg := createEncryptedConfig(t, encryptSvc)
|
||||
store := fakes.NewFakeAlertmanagerConfigStore(cfg)
|
||||
|
||||
@@ -300,7 +300,9 @@ func TestIntegrationAccessControl(t *testing.T) {
|
||||
}
|
||||
|
||||
if tc.canRead {
|
||||
// Set expected metadata.
|
||||
expectedWithMetadata := expected.DeepCopy()
|
||||
expectedWithMetadata.SetInUse(0, nil)
|
||||
if tc.canUpdate {
|
||||
expectedWithMetadata.SetAccessControl("canWrite")
|
||||
}
|
||||
@@ -413,6 +415,132 @@ func TestIntegrationAccessControl(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationInUseMetadata(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
}
|
||||
|
||||
ctx := context.Background()
|
||||
helper := getTestHelper(t)
|
||||
|
||||
cliCfg := helper.Org1.Admin.NewRestConfig()
|
||||
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
||||
|
||||
// Prepare environment and create notification policy and rule that use receiver
|
||||
alertmanagerRaw, err := testData.ReadFile(path.Join("test-data", "notification-settings.json"))
|
||||
require.NoError(t, err)
|
||||
var amConfig definitions.PostableUserConfig
|
||||
require.NoError(t, json.Unmarshal(alertmanagerRaw, &amConfig))
|
||||
|
||||
// Add more references to the receiver in other routes.
|
||||
route1 := *amConfig.AlertmanagerConfig.Route.Routes[0]
|
||||
route1.Routes = nil
|
||||
route2 := route1
|
||||
parentRoute := *amConfig.AlertmanagerConfig.Route.Routes[0]
|
||||
parentRoute.Routes = []*definitions.Route{&route1, &route2}
|
||||
amConfig.AlertmanagerConfig.Route.Routes = append(amConfig.AlertmanagerConfig.Route.Routes, &parentRoute)
|
||||
|
||||
success, err := legacyCli.PostConfiguration(t, amConfig)
|
||||
require.Truef(t, success, "Failed to post Alertmanager configuration: %s", err)
|
||||
|
||||
postGroupRaw, err := testData.ReadFile(path.Join("test-data", "rulegroup-1.json"))
|
||||
require.NoError(t, err)
|
||||
var ruleGroup definitions.PostableRuleGroupConfig
|
||||
require.NoError(t, json.Unmarshal(postGroupRaw, &ruleGroup))
|
||||
|
||||
// Add more references to the receiver by creating adding same rule with a different title.
|
||||
ruleGen := func() definitions.PostableGrafanaRule { return *ruleGroup.Rules[0].GrafanaManagedAlert }
|
||||
rule2 := ruleGen()
|
||||
rule2.Title = "Rule2"
|
||||
rule2.NotificationSettings = &definitions.AlertRuleNotificationSettings{Receiver: "grafana-default-email"}
|
||||
rule3 := ruleGen()
|
||||
rule3.Title = "Rule3"
|
||||
ruleGroup.Rules = append(ruleGroup.Rules,
|
||||
definitions.PostableExtendedRuleNode{
|
||||
ApiRuleNode: ruleGroup.Rules[0].ApiRuleNode,
|
||||
GrafanaManagedAlert: &rule2,
|
||||
},
|
||||
definitions.PostableExtendedRuleNode{
|
||||
ApiRuleNode: ruleGroup.Rules[0].ApiRuleNode,
|
||||
GrafanaManagedAlert: &rule3,
|
||||
},
|
||||
)
|
||||
|
||||
folderUID := "test-folder"
|
||||
legacyCli.CreateFolder(t, folderUID, "TEST")
|
||||
_, status, data := legacyCli.PostRulesGroupWithStatus(t, folderUID, &ruleGroup)
|
||||
require.Equalf(t, http.StatusAccepted, status, "Failed to post Rule: %s", data)
|
||||
|
||||
adminK8sClient, err := versioned.NewForConfig(cliCfg)
|
||||
require.NoError(t, err)
|
||||
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
||||
|
||||
requestReceivers := func(t *testing.T, title string) (v0alpha1.Receiver, v0alpha1.Receiver) {
|
||||
t.Helper()
|
||||
receivers, err := adminClient.List(ctx, v1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, receivers.Items, 2)
|
||||
idx := slices.IndexFunc(receivers.Items, func(interval v0alpha1.Receiver) bool {
|
||||
return interval.Spec.Title == title
|
||||
})
|
||||
receiverListed := receivers.Items[idx]
|
||||
|
||||
receiverGet, err := adminClient.Get(ctx, receiverListed.Name, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
return receiverListed, *receiverGet
|
||||
}
|
||||
|
||||
checkInUse := func(t *testing.T, receiverList, receiverGet v0alpha1.Receiver, routes, rules int) {
|
||||
t.Helper()
|
||||
assert.Equalf(t, fmt.Sprintf("%d", routes), receiverList.Annotations[v0alpha1.InUseAnnotation("routes")], "LIST: Expected %s used by %d routes", receiverList.Spec.Title, routes)
|
||||
assert.Equalf(t, fmt.Sprintf("%d", rules), receiverList.Annotations[v0alpha1.InUseAnnotation("rules")], "LIST: Expected %s used by %d rules", receiverList.Spec.Title, rules)
|
||||
assert.Equalf(t, fmt.Sprintf("%d", routes), receiverGet.Annotations[v0alpha1.InUseAnnotation("routes")], "GET: Expected %s used by %d routes", receiverGet.Spec.Title, routes)
|
||||
assert.Equalf(t, fmt.Sprintf("%d", rules), receiverGet.Annotations[v0alpha1.InUseAnnotation("rules")], "GET: Expected %s used by %d rules", receiverGet.Spec.Title, rules)
|
||||
}
|
||||
|
||||
receiverListed, receiverGet := requestReceivers(t, "user-defined")
|
||||
checkInUse(t, receiverListed, receiverGet, 4, 2)
|
||||
|
||||
// Verify the default.
|
||||
receiverListed, receiverGet = requestReceivers(t, "grafana-default-email")
|
||||
checkInUse(t, receiverListed, receiverGet, 1, 1)
|
||||
|
||||
// Removing the new extra route should leave only 1.
|
||||
amConfig.AlertmanagerConfig.Route.Routes = amConfig.AlertmanagerConfig.Route.Routes[:1]
|
||||
success, err = legacyCli.PostConfiguration(t, amConfig)
|
||||
require.Truef(t, success, "Failed to post Alertmanager configuration: %s", err)
|
||||
|
||||
receiverListed, receiverGet = requestReceivers(t, "user-defined")
|
||||
checkInUse(t, receiverListed, receiverGet, 1, 2)
|
||||
|
||||
// Remove the extra rules.
|
||||
ruleGroup.Rules = ruleGroup.Rules[:1]
|
||||
_, status, data = legacyCli.PostRulesGroupWithStatus(t, folderUID, &ruleGroup)
|
||||
require.Equalf(t, http.StatusAccepted, status, "Failed to post Rule: %s", data)
|
||||
|
||||
receiverListed, receiverGet = requestReceivers(t, "user-defined")
|
||||
checkInUse(t, receiverListed, receiverGet, 1, 1)
|
||||
|
||||
receiverListed, receiverGet = requestReceivers(t, "grafana-default-email")
|
||||
checkInUse(t, receiverListed, receiverGet, 1, 0)
|
||||
|
||||
// Remove the rest.
|
||||
amConfig.AlertmanagerConfig.Route.Routes = nil
|
||||
success, err = legacyCli.PostConfiguration(t, amConfig)
|
||||
require.Truef(t, success, "Failed to post Alertmanager configuration: %s", err)
|
||||
|
||||
ruleGroup.Rules = nil
|
||||
_, status, data = legacyCli.PostRulesGroupWithStatus(t, folderUID, &ruleGroup)
|
||||
require.Equalf(t, http.StatusAccepted, status, "Failed to post Rule: %s", data)
|
||||
|
||||
receiverListed, receiverGet = requestReceivers(t, "user-defined")
|
||||
checkInUse(t, receiverListed, receiverGet, 0, 0)
|
||||
|
||||
receiverListed, receiverGet = requestReceivers(t, "grafana-default-email")
|
||||
checkInUse(t, receiverListed, receiverGet, 1, 0)
|
||||
}
|
||||
|
||||
func TestIntegrationProvisioning(t *testing.T) {
|
||||
if testing.Short() {
|
||||
t.Skip("skipping integration test")
|
||||
@@ -883,9 +1011,10 @@ func TestIntegrationCRUD(t *testing.T) {
|
||||
require.NoError(t, err)
|
||||
require.Len(t, receiver.Spec.Integrations, len(integrations))
|
||||
|
||||
// Set access control metadata
|
||||
// Set expected metadata
|
||||
receiver.SetAccessControl("canWrite")
|
||||
receiver.SetAccessControl("canDelete")
|
||||
receiver.SetInUse(0, nil)
|
||||
|
||||
// Use export endpoint because it's the only way to get decrypted secrets fast.
|
||||
cliCfg := helper.Org1.Admin.NewRestConfig()
|
||||
|
||||
Reference in New Issue
Block a user