diff --git a/pkg/apis/alerting_notifications/v0alpha1/types_ext.go b/pkg/apis/alerting_notifications/v0alpha1/types_ext.go index f40e13668da..70a3e9a8b1d 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/types_ext.go +++ b/pkg/apis/alerting_notifications/v0alpha1/types_ext.go @@ -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) +} diff --git a/pkg/registry/apis/alerting/notifications/receiver/conversions.go b/pkg/registry/apis/alerting/notifications/receiver/conversions.go index f6d71779550..6402db56562 100644 --- a/pkg/registry/apis/alerting/notifications/receiver/conversions.go +++ b/pkg/registry/apis/alerting/notifications/receiver/conversions.go @@ -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 } diff --git a/pkg/registry/apis/alerting/notifications/receiver/legacy_storage.go b/pkg/registry/apis/alerting/notifications/receiver/legacy_storage.go index d859d695bbf..091cf5848f4 100644 --- a/pkg/registry/apis/alerting/notifications/receiver/legacy_storage.go +++ b/pkg/registry/apis/alerting/notifications/receiver/legacy_storage.go @@ -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 } diff --git a/pkg/registry/apis/alerting/notifications/register.go b/pkg/registry/apis/alerting/notifications/register.go index 641fd15c5dc..db0c85ddea3 100644 --- a/pkg/registry/apis/alerting/notifications/register.go +++ b/pkg/registry/apis/alerting/notifications/register.go @@ -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) } diff --git a/pkg/services/ngalert/models/receivers.go b/pkg/services/ngalert/models/receivers.go index 8bfeaafc475..160c07b6b05 100644 --- a/pkg/services/ngalert/models/receivers.go +++ b/pkg/services/ngalert/models/receivers.go @@ -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 diff --git a/pkg/services/ngalert/notifier/legacy_storage/receivers.go b/pkg/services/ngalert/notifier/legacy_storage/receivers.go index e54f2b3acd4..81353609368 100644 --- a/pkg/services/ngalert/notifier/legacy_storage/receivers.go +++ b/pkg/services/ngalert/notifier/legacy_storage/receivers.go @@ -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 { diff --git a/pkg/services/ngalert/notifier/receiver_svc.go b/pkg/services/ngalert/notifier/receiver_svc.go index 73a0ea3fd34..328ee24fdf0 100644 --- a/pkg/services/ngalert/notifier/receiver_svc.go +++ b/pkg/services/ngalert/notifier/receiver_svc.go @@ -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 { diff --git a/pkg/services/ngalert/notifier/receiver_svc_test.go b/pkg/services/ngalert/notifier/receiver_svc_test.go index 309cb2fe352..75621d1c095 100644 --- a/pkg/services/ngalert/notifier/receiver_svc_test.go +++ b/pkg/services/ngalert/notifier/receiver_svc_test.go @@ -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) diff --git a/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go b/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go index 3b9fadd7cff..03d849056e4 100644 --- a/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go +++ b/pkg/tests/apis/alerting/notifications/receivers/receiver_test.go @@ -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()