26162a53e6
Previously all receiver modifications were denied with alertingApiServer enabled. This allows pure creates and deletes through as these specific cases can be handled simply and without risk of rbac shenanigans.
1648 lines
60 KiB
Go
1648 lines
60 KiB
Go
package receivers
|
|
|
|
import (
|
|
"context"
|
|
"embed"
|
|
"encoding/json"
|
|
"fmt"
|
|
"maps"
|
|
"net/http"
|
|
"path"
|
|
"slices"
|
|
"sort"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/grafana/alerting/notify"
|
|
"github.com/prometheus/alertmanager/config"
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
"k8s.io/apimachinery/pkg/api/errors"
|
|
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/types"
|
|
|
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/bus"
|
|
"github.com/grafana/grafana/pkg/generated/clientset/versioned"
|
|
notificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1"
|
|
"github.com/grafana/grafana/pkg/infra/tracing"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/ossaccesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
|
"github.com/grafana/grafana/pkg/services/authz/zanzana"
|
|
"github.com/grafana/grafana/pkg/services/dashboards"
|
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
|
"github.com/grafana/grafana/pkg/services/folder/foldertest"
|
|
alertingac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/api"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
|
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/notifier/channels_config"
|
|
"github.com/grafana/grafana/pkg/services/ngalert/store"
|
|
"github.com/grafana/grafana/pkg/services/org"
|
|
"github.com/grafana/grafana/pkg/tests/api/alerting"
|
|
"github.com/grafana/grafana/pkg/tests/apis"
|
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
|
"github.com/grafana/grafana/pkg/tests/testsuite"
|
|
"github.com/grafana/grafana/pkg/util"
|
|
)
|
|
|
|
//go:embed test-data/*.*
|
|
var testData embed.FS
|
|
|
|
func TestMain(m *testing.M) {
|
|
testsuite.Run(m)
|
|
}
|
|
|
|
func getTestHelper(t *testing.T) *apis.K8sTestHelper {
|
|
return apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
|
EnableFeatureToggles: []string{
|
|
featuremgmt.FlagAlertingApiServer,
|
|
},
|
|
})
|
|
}
|
|
|
|
func TestIntegrationResourceIdentifier(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
|
|
require.NoError(t, err)
|
|
client := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
newResource := &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: "Test-Receiver",
|
|
Integrations: nil,
|
|
},
|
|
}
|
|
|
|
t.Run("create should fail if object name is specified", func(t *testing.T) {
|
|
resource := newResource.DeepCopy()
|
|
resource.Name = "new-receiver"
|
|
_, err := client.Create(ctx, resource, v1.CreateOptions{})
|
|
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest but got %s", err)
|
|
})
|
|
|
|
var resourceID string
|
|
t.Run("create should succeed and provide resource name", func(t *testing.T) {
|
|
actual, err := client.Create(ctx, newResource, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotEmptyf(t, actual.Name, "Resource name should not be empty")
|
|
require.NotEmptyf(t, actual.UID, "Resource UID should not be empty")
|
|
resourceID = actual.Name
|
|
})
|
|
|
|
t.Run("resource should be available by the identifier", func(t *testing.T) {
|
|
actual, err := client.Get(ctx, resourceID, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NotEmptyf(t, actual.Name, "Resource name should not be empty")
|
|
require.Equal(t, newResource.Spec, actual.Spec)
|
|
})
|
|
|
|
t.Run("update should rename receiver if name in the specification changes", func(t *testing.T) {
|
|
existing, err := client.Get(ctx, resourceID, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
updated := existing.DeepCopy()
|
|
updated.Spec.Title = "another-newReceiver"
|
|
|
|
actual, err := client.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, updated.Spec, actual.Spec)
|
|
require.NotEqualf(t, updated.Name, actual.Name, "Update should change the resource name but it didn't")
|
|
require.NotEqualf(t, updated.ResourceVersion, actual.ResourceVersion, "Update should change the resource version but it didn't")
|
|
|
|
resource, err := client.Get(ctx, actual.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, actual.Spec, resource.Spec)
|
|
require.Equal(t, actual.Name, resource.Name)
|
|
require.Equal(t, actual.ResourceVersion, resource.ResourceVersion)
|
|
})
|
|
}
|
|
|
|
// TestIntegrationResourcePermissions focuses on testing resource permissions for the alerting receiver resource. It
|
|
// verifies that access is correctly set when creating resources and assigning permissions to users, teams, and roles.
|
|
func TestIntegrationResourcePermissions(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
org1 := helper.Org1
|
|
|
|
noneUser := helper.CreateUser("none", apis.Org1, org.RoleNone, nil)
|
|
|
|
creator := helper.CreateUser("creator", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
createWildcardPermission(
|
|
accesscontrol.ActionAlertingReceiversCreate,
|
|
),
|
|
})
|
|
|
|
newClient := func(t *testing.T, user apis.User) notificationsv0alpha1.ReceiverInterface {
|
|
k8sClient, err := versioned.NewForConfig(user.NewRestConfig())
|
|
require.NoError(t, err)
|
|
return k8sClient.NotificationsV0alpha1().Receivers("default")
|
|
}
|
|
|
|
admin := org1.Admin
|
|
viewer := org1.Viewer
|
|
editor := org1.Editor
|
|
adminClient := newClient(t, admin)
|
|
|
|
writeACMetadata := []string{"canWrite", "canDelete"}
|
|
allACMetadata := []string{"canWrite", "canDelete", "canReadSecrets", "canAdmin"}
|
|
|
|
mustID := func(user apis.User) int64 {
|
|
id, err := user.Identity.GetInternalID()
|
|
require.NoError(t, err)
|
|
return id
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
creatingUser apis.User
|
|
testUser apis.User
|
|
assignments []accesscontrol.SetResourcePermissionCommand
|
|
expACMetadata []string
|
|
expRead bool
|
|
}{
|
|
// Basic access.
|
|
{
|
|
name: "Admin creates and has all metadata and access",
|
|
creatingUser: admin,
|
|
testUser: admin,
|
|
assignments: nil,
|
|
expACMetadata: allACMetadata,
|
|
expRead: true,
|
|
},
|
|
{
|
|
name: "Creator creates and has all metadata and access",
|
|
creatingUser: creator,
|
|
testUser: creator,
|
|
assignments: nil,
|
|
expACMetadata: allACMetadata,
|
|
expRead: true,
|
|
},
|
|
{
|
|
name: "Admin creates, noneUser has no metadata and no access",
|
|
creatingUser: admin,
|
|
testUser: noneUser,
|
|
assignments: nil,
|
|
expACMetadata: nil,
|
|
expRead: false,
|
|
},
|
|
{
|
|
name: "Admin creates, viewer has no metadata but has access",
|
|
creatingUser: admin,
|
|
testUser: viewer,
|
|
expACMetadata: nil,
|
|
expRead: true,
|
|
},
|
|
{
|
|
name: "Admin creates, editor has write metadata and access",
|
|
creatingUser: admin,
|
|
testUser: editor,
|
|
expACMetadata: writeACMetadata,
|
|
expRead: true,
|
|
},
|
|
// User-based assignments.
|
|
{
|
|
name: "Admin creates, assigns read, noneUser has no metadata but has access",
|
|
creatingUser: admin,
|
|
testUser: noneUser,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionView)}},
|
|
expACMetadata: nil,
|
|
expRead: true,
|
|
},
|
|
{
|
|
name: "Admin creates, assigns write, noneUser has write metadata and access",
|
|
creatingUser: admin,
|
|
testUser: noneUser,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionEdit)}},
|
|
expACMetadata: writeACMetadata,
|
|
expRead: true,
|
|
},
|
|
{
|
|
name: "Admin creates, assigns admin, noneUser has all metadata and access",
|
|
creatingUser: admin,
|
|
testUser: noneUser,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
|
expACMetadata: allACMetadata,
|
|
expRead: true,
|
|
},
|
|
// Other users don't get assignments.
|
|
{
|
|
name: "Admin creates, assigns read to noneUser, creator has no metadata and no access",
|
|
creatingUser: admin,
|
|
testUser: creator,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionView)}},
|
|
expACMetadata: nil,
|
|
expRead: false,
|
|
},
|
|
{
|
|
name: "Admin creates, assigns write to noneUser, creator has no metadata and no access",
|
|
creatingUser: admin,
|
|
testUser: creator,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionEdit)}},
|
|
expACMetadata: nil,
|
|
expRead: false,
|
|
},
|
|
{
|
|
name: "Admin creates, assigns admin to noneUser, creator has no metadata and no access",
|
|
creatingUser: admin,
|
|
testUser: creator,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(noneUser), Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
|
expACMetadata: nil,
|
|
expRead: false,
|
|
},
|
|
// Role-based access.
|
|
{
|
|
name: "Admin creates, assigns editor, viewer has write metadata and access",
|
|
creatingUser: admin,
|
|
testUser: viewer,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(viewer), Permission: string(alertingac.ReceiverPermissionEdit)}},
|
|
expACMetadata: writeACMetadata,
|
|
expRead: true,
|
|
},
|
|
{
|
|
name: "Admin creates, assigns admin, viewer has all metadata and access",
|
|
creatingUser: admin,
|
|
testUser: viewer,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(viewer), Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
|
expACMetadata: allACMetadata,
|
|
expRead: true,
|
|
},
|
|
{
|
|
name: "Admin creates, assigns admin, editor has all metadata and access",
|
|
creatingUser: admin,
|
|
testUser: editor,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{UserID: mustID(editor), Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
|
expACMetadata: allACMetadata,
|
|
expRead: true,
|
|
},
|
|
// Team-based access. Staff team has editor+admin but not viewer in it.
|
|
{
|
|
name: "Admin creates, assigns admin to staff, viewer has no metadata and access",
|
|
creatingUser: admin,
|
|
testUser: viewer,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{TeamID: org1.Staff.ID, Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
|
expACMetadata: nil,
|
|
expRead: true,
|
|
},
|
|
{
|
|
name: "Admin creates, assigns admin to staff, editor has all metadata and access",
|
|
creatingUser: admin,
|
|
testUser: editor,
|
|
assignments: []accesscontrol.SetResourcePermissionCommand{{TeamID: org1.Staff.ID, Permission: string(alertingac.ReceiverPermissionAdmin)}},
|
|
expACMetadata: allACMetadata,
|
|
expRead: true,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
createClient := newClient(t, tc.creatingUser)
|
|
client := newClient(t, tc.testUser)
|
|
|
|
var created = &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: "receiver-1",
|
|
Integrations: nil,
|
|
},
|
|
}
|
|
d, err := json.Marshal(created)
|
|
require.NoError(t, err)
|
|
|
|
// Create receiver with creatingUser
|
|
created, err = createClient.Create(ctx, created, v1.CreateOptions{})
|
|
require.NoErrorf(t, err, "Payload %s", string(d))
|
|
require.NotNil(t, created)
|
|
|
|
defer func() {
|
|
_ = adminClient.Delete(ctx, created.Name, v1.DeleteOptions{})
|
|
}()
|
|
|
|
// Assign resource permissions
|
|
cliCfg := helper.Org1.Admin.NewRestConfig()
|
|
alertingApi := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
|
for _, permission := range tc.assignments {
|
|
status, body := alertingApi.AssignReceiverPermission(t, created.Name, permission)
|
|
require.Equalf(t, http.StatusOK, status, "Expected status 200 but got %d: %s", status, body)
|
|
}
|
|
|
|
// Test read
|
|
if tc.expRead {
|
|
// Helper methods.
|
|
extractReceiverFromList := func(list *v0alpha1.ReceiverList, name string) *v0alpha1.Receiver {
|
|
for i := range list.Items {
|
|
if list.Items[i].Name == name {
|
|
return list.Items[i].DeepCopy()
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Obtain expected responses using admin client as source of truth.
|
|
expectedGetWithMetadata, expectedListWithMetadata := func() (*v0alpha1.Receiver, *v0alpha1.Receiver) {
|
|
expectedGet, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, expectedGet)
|
|
|
|
// Set expected metadata.
|
|
expectedGetWithMetadata := expectedGet.DeepCopy()
|
|
// Clear any existing access control metadata.
|
|
for _, k := range allACMetadata {
|
|
delete(expectedGetWithMetadata.Annotations, v0alpha1.AccessControlAnnotation(k))
|
|
}
|
|
for _, ac := range tc.expACMetadata {
|
|
expectedGetWithMetadata.SetAccessControl(ac)
|
|
}
|
|
|
|
expectedList, err := adminClient.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
expectedListWithMetadata := extractReceiverFromList(expectedList, created.Name)
|
|
require.NotNil(t, expectedListWithMetadata)
|
|
expectedListWithMetadata = expectedListWithMetadata.DeepCopy()
|
|
// Clear any existing access control metadata.
|
|
for _, k := range allACMetadata {
|
|
delete(expectedListWithMetadata.Annotations, v0alpha1.AccessControlAnnotation(k))
|
|
}
|
|
for _, ac := range tc.expACMetadata {
|
|
expectedListWithMetadata.SetAccessControl(ac)
|
|
}
|
|
return expectedGetWithMetadata, expectedListWithMetadata
|
|
}()
|
|
|
|
t.Run("should be able to list receivers", func(t *testing.T) {
|
|
list, err := client.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
listedReceiver := extractReceiverFromList(list, created.Name)
|
|
assert.Equalf(t, expectedListWithMetadata, listedReceiver, "Expected %v but got %v", expectedListWithMetadata, listedReceiver)
|
|
})
|
|
|
|
t.Run("should be able to read receiver by resource identifier", func(t *testing.T) {
|
|
got, err := client.Get(ctx, expectedGetWithMetadata.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
assert.Equalf(t, expectedGetWithMetadata, got, "Expected %v but got %v", expectedGetWithMetadata, got)
|
|
})
|
|
} else {
|
|
t.Run("list receivers should be empty", func(t *testing.T) {
|
|
list, err := client.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Emptyf(t, list.Items, "Expected no receivers but got %v", list.Items)
|
|
})
|
|
|
|
t.Run("should be forbidden to read receiver by name", func(t *testing.T) {
|
|
_, err := client.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestIntegrationAccessControl(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
org1 := helper.Org1
|
|
|
|
type testCase struct {
|
|
user apis.User
|
|
canRead bool
|
|
canUpdate bool
|
|
canCreate bool
|
|
canDelete bool
|
|
canReadSecrets bool
|
|
canAdmin bool
|
|
}
|
|
// region users
|
|
unauthorized := helper.CreateUser("unauthorized", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{})
|
|
|
|
reader := helper.CreateUser("reader", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
createWildcardPermission(accesscontrol.ActionAlertingReceiversRead),
|
|
})
|
|
secretsReader := helper.CreateUser("secretsReader", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
createWildcardPermission(accesscontrol.ActionAlertingReceiversReadSecrets),
|
|
})
|
|
creator := helper.CreateUser("creator", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
createWildcardPermission(
|
|
accesscontrol.ActionAlertingReceiversRead,
|
|
accesscontrol.ActionAlertingReceiversCreate,
|
|
),
|
|
})
|
|
updater := helper.CreateUser("updater", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
createWildcardPermission(
|
|
accesscontrol.ActionAlertingReceiversRead,
|
|
accesscontrol.ActionAlertingReceiversUpdate,
|
|
),
|
|
})
|
|
deleter := helper.CreateUser("deleter", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
createWildcardPermission(
|
|
accesscontrol.ActionAlertingReceiversRead,
|
|
accesscontrol.ActionAlertingReceiversDelete,
|
|
),
|
|
})
|
|
legacyReader := helper.CreateUser("legacyReader", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
{
|
|
Actions: []string{
|
|
accesscontrol.ActionAlertingNotificationsRead,
|
|
},
|
|
},
|
|
})
|
|
legacyWriter := helper.CreateUser("legacyWriter", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
{
|
|
Actions: []string{
|
|
accesscontrol.ActionAlertingNotificationsRead,
|
|
accesscontrol.ActionAlertingNotificationsWrite,
|
|
},
|
|
},
|
|
})
|
|
adminLikeUser := helper.CreateUser("adminLikeUser", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
createWildcardPermission(append(
|
|
[]string{accesscontrol.ActionAlertingReceiversCreate},
|
|
ossaccesscontrol.ReceiversAdminActions...,
|
|
)...),
|
|
})
|
|
|
|
// Test receivers with uids longer than 40 characters. User name is used in receiver name.
|
|
adminLikeUserLongName := helper.CreateUser("adminLikeUserCreatingAReallyLongReceiverName", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
|
createWildcardPermission(append(
|
|
[]string{accesscontrol.ActionAlertingReceiversCreate},
|
|
ossaccesscontrol.ReceiversAdminActions...,
|
|
)...),
|
|
})
|
|
|
|
// endregion
|
|
|
|
testCases := []testCase{
|
|
{
|
|
user: unauthorized,
|
|
canRead: false,
|
|
canUpdate: false,
|
|
canCreate: false,
|
|
canDelete: false,
|
|
},
|
|
{
|
|
user: org1.Admin,
|
|
canRead: true,
|
|
canCreate: true,
|
|
canUpdate: true,
|
|
canDelete: true,
|
|
canAdmin: true,
|
|
canReadSecrets: true,
|
|
},
|
|
{
|
|
user: org1.Editor,
|
|
canRead: true,
|
|
canUpdate: true,
|
|
canCreate: true,
|
|
canDelete: true,
|
|
},
|
|
{
|
|
user: org1.Viewer,
|
|
canRead: true,
|
|
},
|
|
{
|
|
user: reader,
|
|
canRead: true,
|
|
},
|
|
{
|
|
user: secretsReader,
|
|
canRead: true,
|
|
canReadSecrets: true,
|
|
},
|
|
{
|
|
user: creator,
|
|
canRead: true,
|
|
canCreate: true,
|
|
},
|
|
{
|
|
user: updater,
|
|
canRead: true,
|
|
canUpdate: true,
|
|
},
|
|
{
|
|
user: deleter,
|
|
canRead: true,
|
|
canDelete: true,
|
|
},
|
|
{
|
|
user: legacyReader,
|
|
canRead: true,
|
|
},
|
|
{
|
|
user: legacyWriter,
|
|
canRead: true,
|
|
canCreate: true,
|
|
canUpdate: true,
|
|
canDelete: true,
|
|
},
|
|
{
|
|
user: adminLikeUser,
|
|
canRead: true,
|
|
canCreate: true,
|
|
canUpdate: true,
|
|
canDelete: true,
|
|
canAdmin: true,
|
|
canReadSecrets: true,
|
|
},
|
|
{
|
|
user: adminLikeUserLongName,
|
|
canRead: true,
|
|
canCreate: true,
|
|
canUpdate: true,
|
|
canDelete: true,
|
|
canAdmin: true,
|
|
canReadSecrets: true,
|
|
},
|
|
}
|
|
|
|
admin := org1.Admin
|
|
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
for _, tc := range testCases {
|
|
t.Run(fmt.Sprintf("user '%s'", tc.user.Identity.GetLogin()), func(t *testing.T) {
|
|
k8sClient, err := versioned.NewForConfig(tc.user.NewRestConfig())
|
|
require.NoError(t, err)
|
|
client := k8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
var expected = &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: fmt.Sprintf("receiver-1-%s", tc.user.Identity.GetLogin()),
|
|
Integrations: nil,
|
|
},
|
|
}
|
|
d, err := json.Marshal(expected)
|
|
require.NoError(t, err)
|
|
|
|
newReceiver := expected.DeepCopy()
|
|
newReceiver.Spec.Title = fmt.Sprintf("receiver-2-%s", tc.user.Identity.GetLogin())
|
|
if tc.canCreate {
|
|
t.Run("should be able to create receiver", func(t *testing.T) {
|
|
actual, err := client.Create(ctx, newReceiver, v1.CreateOptions{})
|
|
require.NoErrorf(t, err, "Payload %s", string(d))
|
|
|
|
require.Equal(t, newReceiver.Spec, actual.Spec)
|
|
|
|
t.Run("should fail if already exists", func(t *testing.T) {
|
|
_, err := client.Create(ctx, newReceiver, v1.CreateOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "expected bad request but got %s", err)
|
|
})
|
|
|
|
// Cleanup.
|
|
require.NoError(t, adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{}))
|
|
})
|
|
} else {
|
|
t.Run("should be forbidden to create", func(t *testing.T) {
|
|
_, err := client.Create(ctx, newReceiver, v1.CreateOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "Payload %s", string(d))
|
|
})
|
|
}
|
|
|
|
// create resource to proceed with other tests. We don't use the one created above because the user will always
|
|
// have admin permissions on it.
|
|
expected, err = adminClient.Create(ctx, expected, v1.CreateOptions{})
|
|
require.NoErrorf(t, err, "Payload %s", string(d))
|
|
require.NotNil(t, expected)
|
|
|
|
if tc.canRead {
|
|
// Set expected metadata.
|
|
expectedWithMetadata := expected.DeepCopy()
|
|
expectedWithMetadata.SetInUse(0, nil)
|
|
if tc.canUpdate {
|
|
expectedWithMetadata.SetAccessControl("canWrite")
|
|
}
|
|
if tc.canDelete {
|
|
expectedWithMetadata.SetAccessControl("canDelete")
|
|
}
|
|
if tc.canReadSecrets {
|
|
expectedWithMetadata.SetAccessControl("canReadSecrets")
|
|
}
|
|
if tc.canAdmin {
|
|
expectedWithMetadata.SetAccessControl("canAdmin")
|
|
}
|
|
t.Run("should be able to list receivers", func(t *testing.T) {
|
|
list, err := client.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 2) // default + created
|
|
})
|
|
|
|
t.Run("should be able to read receiver by resource identifier", func(t *testing.T) {
|
|
got, err := client.Get(ctx, expected.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, expectedWithMetadata, got)
|
|
|
|
t.Run("should get NotFound if resource does not exist", func(t *testing.T) {
|
|
_, err := client.Get(ctx, "Notfound", v1.GetOptions{})
|
|
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
|
})
|
|
})
|
|
} else {
|
|
t.Run("list receivers should be empty", func(t *testing.T) {
|
|
list, err := client.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Emptyf(t, list.Items, "Expected no receivers but got %v", list.Items)
|
|
})
|
|
|
|
t.Run("should be forbidden to read receiver by name", func(t *testing.T) {
|
|
_, err := client.Get(ctx, expected.Name, v1.GetOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
|
|
t.Run("should get forbidden even if name does not exist", func(t *testing.T) {
|
|
_, err := client.Get(ctx, "Notfound", v1.GetOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
})
|
|
}
|
|
|
|
updatedExpected := expected.DeepCopy()
|
|
updatedExpected.Spec.Integrations = append(updatedExpected.Spec.Integrations, createIntegration(t, "email"))
|
|
|
|
d, err = json.Marshal(updatedExpected)
|
|
require.NoError(t, err)
|
|
|
|
if tc.canUpdate {
|
|
t.Run("should be able to update receiver", func(t *testing.T) {
|
|
updated, err := client.Update(ctx, updatedExpected, v1.UpdateOptions{})
|
|
require.NoErrorf(t, err, "Payload %s", string(d))
|
|
|
|
expected = updated
|
|
|
|
t.Run("should get NotFound if name does not exist", func(t *testing.T) {
|
|
up := updatedExpected.DeepCopy()
|
|
up.Name = "notFound"
|
|
_, err := client.Update(ctx, up, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
|
})
|
|
})
|
|
} else {
|
|
t.Run("should be forbidden to update receiver", func(t *testing.T) {
|
|
_, err := client.Update(ctx, updatedExpected, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
|
|
t.Run("should get forbidden even if resource does not exist", func(t *testing.T) {
|
|
up := updatedExpected.DeepCopy()
|
|
up.Name = "notFound"
|
|
_, err := client.Update(ctx, up, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
})
|
|
}
|
|
|
|
deleteOptions := v1.DeleteOptions{Preconditions: &v1.Preconditions{ResourceVersion: util.Pointer(expected.ResourceVersion)}}
|
|
|
|
if tc.canDelete {
|
|
t.Run("should be able to delete receiver", func(t *testing.T) {
|
|
err := client.Delete(ctx, expected.Name, deleteOptions)
|
|
require.NoError(t, err)
|
|
|
|
t.Run("should get NotFound if name does not exist", func(t *testing.T) {
|
|
err := client.Delete(ctx, "notfound", v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
|
})
|
|
})
|
|
} else {
|
|
t.Run("should be forbidden to delete receiver", func(t *testing.T) {
|
|
err := client.Delete(ctx, expected.Name, deleteOptions)
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
|
|
t.Run("should be forbidden even if resource does not exist", func(t *testing.T) {
|
|
err := client.Delete(ctx, "notfound", v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
})
|
|
require.NoError(t, adminClient.Delete(ctx, expected.Name, v1.DeleteOptions{}))
|
|
}
|
|
|
|
if tc.canRead {
|
|
t.Run("should get empty list if no receivers", func(t *testing.T) {
|
|
list, err := client.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
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)
|
|
|
|
adminK8sClient, err := versioned.NewForConfig(cliCfg)
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
// 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)
|
|
|
|
persistInitialConfig(t, amConfig, adminClient, legacyCli)
|
|
|
|
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)
|
|
|
|
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")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
org := helper.Org1
|
|
|
|
admin := org.Admin
|
|
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
env := helper.GetEnv()
|
|
ac := acimpl.ProvideAccessControl(env.FeatureToggles, zanzana.NewNoopClient())
|
|
db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
|
|
require.NoError(t, err)
|
|
|
|
created, err := adminClient.Create(ctx, &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: "test-receiver-1",
|
|
Integrations: []v0alpha1.Integration{
|
|
createIntegration(t, "email"),
|
|
},
|
|
},
|
|
}, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "none", created.GetProvenanceStatus())
|
|
|
|
t.Run("should provide provenance status", func(t *testing.T) {
|
|
require.NoError(t, db.SetProvenance(ctx, &definitions.EmbeddedContactPoint{
|
|
UID: *created.Spec.Integrations[0].Uid,
|
|
}, admin.Identity.GetOrgID(), "API"))
|
|
|
|
got, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, "API", got.GetProvenanceStatus())
|
|
})
|
|
|
|
t.Run("should not let update if provisioned", func(t *testing.T) {
|
|
got, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
updated := got.DeepCopy()
|
|
updated.Spec.Integrations = append(updated.Spec.Integrations, createIntegration(t, "email"))
|
|
|
|
_, err = adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
|
|
t.Run("should not let delete if provisioned", func(t *testing.T) {
|
|
err := adminClient.Delete(ctx, created.Name, v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationOptimisticConcurrency(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
receiver := v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: "receiver-1",
|
|
Integrations: nil,
|
|
},
|
|
}
|
|
|
|
created, err := adminClient.Create(ctx, &receiver, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, created)
|
|
require.NotEmpty(t, created.ResourceVersion)
|
|
|
|
t.Run("should forbid if version does not match", func(t *testing.T) {
|
|
updated := created.DeepCopy()
|
|
updated.ResourceVersion = "test"
|
|
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
t.Run("should update if version matches", func(t *testing.T) {
|
|
updated := created.DeepCopy()
|
|
updated.Spec.Integrations = append(updated.Spec.Integrations, createIntegration(t, "email"))
|
|
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
for i, integration := range actualUpdated.Spec.Integrations {
|
|
updated.Spec.Integrations[i].Uid = integration.Uid
|
|
}
|
|
require.EqualValues(t, updated.Spec, actualUpdated.Spec)
|
|
require.NotEqual(t, updated.ResourceVersion, actualUpdated.ResourceVersion)
|
|
})
|
|
t.Run("should fail to update if version is empty", func(t *testing.T) {
|
|
updated := created.DeepCopy()
|
|
updated.ResourceVersion = ""
|
|
updated.Spec.Integrations = append(updated.Spec.Integrations, createIntegration(t, "webhook"))
|
|
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err) // TODO Change that? K8s returns 400 instead.
|
|
})
|
|
t.Run("should fail to delete if version does not match", func(t *testing.T) {
|
|
actual, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
|
Preconditions: &v1.Preconditions{
|
|
ResourceVersion: util.Pointer("something"),
|
|
},
|
|
})
|
|
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
|
|
})
|
|
t.Run("should succeed if version matches", func(t *testing.T) {
|
|
actual, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
|
Preconditions: &v1.Preconditions{
|
|
ResourceVersion: util.Pointer(actual.ResourceVersion),
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
t.Run("should succeed if version is empty", func(t *testing.T) {
|
|
actual, err := adminClient.Create(ctx, &receiver, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
|
Preconditions: &v1.Preconditions{
|
|
ResourceVersion: util.Pointer(actual.ResourceVersion),
|
|
},
|
|
})
|
|
require.NoError(t, err)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationPatch(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
receiver := v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: "receiver",
|
|
Integrations: []v0alpha1.Integration{
|
|
createIntegration(t, "email"),
|
|
createIntegration(t, "webhook"),
|
|
createIntegration(t, "sns"),
|
|
},
|
|
},
|
|
}
|
|
|
|
current, err := adminClient.Create(ctx, &receiver, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.NotNil(t, current)
|
|
|
|
t.Run("should patch with json patch", func(t *testing.T) {
|
|
current, err := adminClient.Get(ctx, current.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
index := slices.IndexFunc(current.Spec.Integrations, func(t v0alpha1.Integration) bool {
|
|
return t.Type == "webhook"
|
|
})
|
|
|
|
patch := []map[string]any{
|
|
{
|
|
"op": "remove",
|
|
"path": fmt.Sprintf("/spec/integrations/%d/settings/username", index),
|
|
},
|
|
{
|
|
"op": "remove",
|
|
"path": fmt.Sprintf("/spec/integrations/%d/secureFields/password", index),
|
|
},
|
|
{
|
|
"op": "replace",
|
|
"path": fmt.Sprintf("/spec/integrations/%d/settings/authorization_scheme", index),
|
|
"value": "bearer",
|
|
},
|
|
{
|
|
"op": "add",
|
|
"path": fmt.Sprintf("/spec/integrations/%d/settings/authorization_credentials", index),
|
|
"value": "authz-token",
|
|
},
|
|
{
|
|
"op": "remove",
|
|
"path": fmt.Sprintf("/spec/integrations/%d/secureFields/authorization_credentials", index),
|
|
},
|
|
}
|
|
|
|
expected := current.Spec.Integrations[index]
|
|
expected.Settings.Remove("username")
|
|
expected.Settings.Remove("password")
|
|
expected.Settings.Set("authorization_scheme", "bearer")
|
|
delete(expected.SecureFields, "password")
|
|
expected.SecureFields["authorization_credentials"] = true
|
|
|
|
patchData, err := json.Marshal(patch)
|
|
require.NoError(t, err)
|
|
|
|
result, err := adminClient.Patch(ctx, current.Name, types.JSONPatchType, patchData, v1.PatchOptions{})
|
|
require.NoError(t, err)
|
|
|
|
require.EqualValues(t, expected, result.Spec.Integrations[index])
|
|
|
|
// Use export endpoint because it's the only way to get decrypted secrets fast.
|
|
cliCfg := helper.Org1.Admin.NewRestConfig()
|
|
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
|
|
|
export := legacyCli.ExportReceiverTyped(t, current.Spec.Title, true)
|
|
// we need special unmarshaler to parse settings
|
|
cp, err := api.ContactPointFromContactPointExport(export)
|
|
require.NoError(t, err)
|
|
|
|
assert.Len(t, cp.Sns, 1)
|
|
assert.Len(t, cp.Email, 1)
|
|
require.Len(t, cp.Webhook, 1)
|
|
|
|
settings := cp.Webhook[0]
|
|
assert.EqualValues(t, "authz-token", *settings.AuthorizationCredentials)
|
|
assert.EqualValues(t, "bearer", *settings.AuthorizationScheme)
|
|
assert.Nil(t, settings.Password)
|
|
assert.Nil(t, settings.User)
|
|
})
|
|
}
|
|
|
|
func TestIntegrationRejectConfigApiReceiverModification(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
helper := getTestHelper(t)
|
|
|
|
cliCfg := helper.Org1.Admin.NewRestConfig()
|
|
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
|
|
|
adminK8sClient, err := versioned.NewForConfig(cliCfg)
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
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))
|
|
|
|
persistInitialConfig(t, amConfig, adminClient, legacyCli)
|
|
|
|
// We modify the receiver, this should cause the POST to be rejected.
|
|
userDefinedReceiver := amConfig.AlertmanagerConfig.Receivers[slices.IndexFunc(amConfig.AlertmanagerConfig.Receivers, func(receiver *definitions.PostableApiReceiver) bool {
|
|
return receiver.Receiver.Name == "user-defined"
|
|
})]
|
|
userDefinedReceiver.GrafanaManagedReceivers[0].DisableResolveMessage = !userDefinedReceiver.GrafanaManagedReceivers[0].DisableResolveMessage
|
|
success, err := legacyCli.PostConfiguration(t, amConfig)
|
|
require.Falsef(t, success, "Expected receiver modification to be rejected, but got %t", success)
|
|
require.ErrorContainsf(t, err, "alertingApiServer", "Expected error message to contain 'alertingApiServer', but got %s", err)
|
|
|
|
// Revert the change.
|
|
userDefinedReceiver.GrafanaManagedReceivers[0].DisableResolveMessage = !userDefinedReceiver.GrafanaManagedReceivers[0].DisableResolveMessage
|
|
|
|
// We add a receiver, this should be accepted.
|
|
amConfig.AlertmanagerConfig.Receivers = append(amConfig.AlertmanagerConfig.Receivers, &definitions.PostableApiReceiver{
|
|
Receiver: config.Receiver{
|
|
Name: "new receiver",
|
|
},
|
|
PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{
|
|
GrafanaManagedReceivers: []*definitions.PostableGrafanaReceiver{
|
|
{
|
|
Name: "new receiver",
|
|
Type: "email",
|
|
Settings: []byte(`{"addresses": "<some@email.com>"}`),
|
|
},
|
|
},
|
|
},
|
|
})
|
|
success, err = legacyCli.PostConfiguration(t, amConfig)
|
|
require.Truef(t, success, "Expected receiver modification to be accepted, but got %s", err)
|
|
require.NoError(t, err)
|
|
|
|
// Sanity check.
|
|
gettable, status, body := legacyCli.GetAlertmanagerConfigWithStatus(t)
|
|
require.Equalf(t, http.StatusOK, status, body)
|
|
require.Lenf(t, gettable.AlertmanagerConfig.Receivers, 3, "Expected 3 receivers, got %d", len(gettable.AlertmanagerConfig.Receivers))
|
|
|
|
// We remove the receiver, this should be accepted.
|
|
amConfig.AlertmanagerConfig.Receivers = slices.DeleteFunc(amConfig.AlertmanagerConfig.Receivers, func(receiver *definitions.PostableApiReceiver) bool {
|
|
return receiver.GrafanaManagedReceivers[0].Name == "new receiver"
|
|
})
|
|
success, err = legacyCli.PostConfiguration(t, amConfig)
|
|
require.Truef(t, success, "Expected receiver modification to be accepted, but got %s", err)
|
|
require.NoError(t, err)
|
|
|
|
// Sanity check.
|
|
gettable, status, body = legacyCli.GetAlertmanagerConfigWithStatus(t)
|
|
require.Equalf(t, http.StatusOK, status, body)
|
|
require.Lenf(t, gettable.AlertmanagerConfig.Receivers, 2, "Expected 2 receivers, got %d", len(gettable.AlertmanagerConfig.Receivers))
|
|
}
|
|
|
|
func TestIntegrationReferentialIntegrity(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
env := helper.GetEnv()
|
|
ac := acimpl.ProvideAccessControl(env.FeatureToggles, zanzana.NewNoopClient())
|
|
db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
|
|
require.NoError(t, err)
|
|
orgID := helper.Org1.Admin.Identity.GetOrgID()
|
|
|
|
cliCfg := helper.Org1.Admin.NewRestConfig()
|
|
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
|
|
|
adminK8sClient, err := versioned.NewForConfig(cliCfg)
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
// Prepare environment and create notification policy and rule that use time 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))
|
|
|
|
persistInitialConfig(t, amConfig, adminClient, legacyCli)
|
|
|
|
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))
|
|
|
|
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)
|
|
|
|
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 == "user-defined"
|
|
})
|
|
receiver := receivers.Items[idx]
|
|
|
|
currentRoute := legacyCli.GetRoute(t)
|
|
currentRuleGroup := legacyCli.GetRulesGroup(t, folderUID, ruleGroup.Name)
|
|
|
|
t.Run("Update", func(t *testing.T) {
|
|
t.Run("should rename all references if name changes", func(t *testing.T) {
|
|
renamed := receiver.DeepCopy()
|
|
expectedTitle := renamed.Spec.Title + "-new"
|
|
renamed.Spec.Title = expectedTitle
|
|
|
|
actual, err := adminClient.Update(ctx, renamed, v1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
updatedRuleGroup := legacyCli.GetRulesGroup(t, folderUID, ruleGroup.Name)
|
|
for idx, rule := range updatedRuleGroup.Rules {
|
|
assert.Equalf(t, expectedTitle, rule.GrafanaManagedAlert.NotificationSettings.Receiver, "receiver in rule %d should have been renamed but it did not", idx)
|
|
}
|
|
|
|
updatedRoute := legacyCli.GetRoute(t)
|
|
for _, route := range updatedRoute.Routes {
|
|
assert.Equalf(t, expectedTitle, route.Receiver, "time receiver in routes should have been renamed but it did not")
|
|
}
|
|
|
|
actual, err = adminClient.Get(ctx, actual.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
|
|
receiver = *actual
|
|
})
|
|
|
|
t.Run("should fail if at least one resource is provisioned", func(t *testing.T) {
|
|
require.NoError(t, err)
|
|
renamed := receiver.DeepCopy()
|
|
renamed.Spec.Title += util.GenerateShortUID()
|
|
|
|
t.Run("provisioned route", func(t *testing.T) {
|
|
require.NoError(t, db.SetProvenance(ctx, ¤tRoute, orgID, "API"))
|
|
t.Cleanup(func() {
|
|
require.NoError(t, db.DeleteProvenance(ctx, ¤tRoute, orgID))
|
|
})
|
|
actual, err := adminClient.Update(ctx, renamed, v1.UpdateOptions{})
|
|
require.Errorf(t, err, "Expected error but got successful result: %v", actual)
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
|
|
t.Run("provisioned rules", func(t *testing.T) {
|
|
ruleUid := currentRuleGroup.Rules[0].GrafanaManagedAlert.UID
|
|
resource := &ngmodels.AlertRule{UID: ruleUid}
|
|
require.NoError(t, db.SetProvenance(ctx, resource, orgID, "API"))
|
|
t.Cleanup(func() {
|
|
require.NoError(t, db.DeleteProvenance(ctx, resource, orgID))
|
|
})
|
|
|
|
actual, err := adminClient.Update(ctx, renamed, v1.UpdateOptions{})
|
|
require.Errorf(t, err, "Expected error but got successful result: %v", actual)
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
})
|
|
})
|
|
|
|
t.Run("Delete", func(t *testing.T) {
|
|
t.Run("should fail to delete if receiver is used in rule and routes", func(t *testing.T) {
|
|
err := adminClient.Delete(ctx, receiver.Name, v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
|
|
t.Run("should fail to delete if receiver is used in only rule", func(t *testing.T) {
|
|
route := legacyCli.GetRoute(t)
|
|
route.Routes[0].Receiver = ""
|
|
legacyCli.UpdateRoute(t, route, true)
|
|
|
|
err = adminClient.Delete(ctx, receiver.Name, v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
})
|
|
}
|
|
|
|
func TestIntegrationCRUD(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
var defaultReceiver *v0alpha1.Receiver
|
|
t.Run("should list the default receiver", func(t *testing.T) {
|
|
items, err := adminClient.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
assert.Len(t, items.Items, 1)
|
|
defaultReceiver = &items.Items[0]
|
|
assert.Equal(t, "grafana-default-email", defaultReceiver.Spec.Title)
|
|
assert.NotEmpty(t, defaultReceiver.UID)
|
|
assert.NotEmpty(t, defaultReceiver.Name)
|
|
assert.NotEmpty(t, defaultReceiver.ResourceVersion)
|
|
|
|
defaultReceiver, err = adminClient.Get(ctx, defaultReceiver.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
assert.NotEmpty(t, defaultReceiver.UID)
|
|
assert.NotEmpty(t, defaultReceiver.Name)
|
|
assert.NotEmpty(t, defaultReceiver.ResourceVersion)
|
|
assert.Len(t, defaultReceiver.Spec.Integrations, 1)
|
|
})
|
|
|
|
t.Run("should be able to update default receiver", func(t *testing.T) {
|
|
require.NotNil(t, defaultReceiver)
|
|
newDefault := defaultReceiver.DeepCopy()
|
|
newDefault.Spec.Integrations = append(newDefault.Spec.Integrations, createIntegration(t, "line"))
|
|
|
|
updatedReceiver, err := adminClient.Update(ctx, newDefault, v1.UpdateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
expected := newDefault.DeepCopy()
|
|
expected.Spec.Integrations[0].Uid = updatedReceiver.Spec.Integrations[0].Uid // default integration does not have UID before first update
|
|
lineIntegration := expected.Spec.Integrations[1]
|
|
lineIntegration.SecureFields = map[string]bool{
|
|
"token": true,
|
|
}
|
|
lineIntegration.Settings.Remove("token")
|
|
assert.Equal(t, "LINE", updatedReceiver.Spec.Integrations[1].Type) // this type is in the schema but not in backend
|
|
lineIntegration.Type = "LINE"
|
|
lineIntegration.Uid = updatedReceiver.Spec.Integrations[1].Uid
|
|
expected.Spec.Integrations[1] = lineIntegration
|
|
|
|
assert.Equal(t, expected.Spec, updatedReceiver.Spec)
|
|
})
|
|
|
|
t.Run("should fail to create receiver with the existing name", func(t *testing.T) {
|
|
newReceiver := &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: defaultReceiver.Spec.Title,
|
|
Integrations: nil,
|
|
},
|
|
}
|
|
_, err := adminClient.Create(ctx, newReceiver, v1.CreateOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
|
|
t.Run("should not let delete default receiver", func(t *testing.T) {
|
|
err := adminClient.Delete(ctx, defaultReceiver.Name, v1.DeleteOptions{})
|
|
require.Truef(t, errors.IsConflict(err), "Expected Conflict, got: %s", err)
|
|
})
|
|
|
|
var receiver *v0alpha1.Receiver
|
|
t.Run("should correctly persist all known integrations", func(t *testing.T) {
|
|
integrations := make([]v0alpha1.Integration, 0, len(notify.AllKnownConfigsForTesting))
|
|
keysIter := maps.Keys(notify.AllKnownConfigsForTesting)
|
|
keys := slices.Collect(keysIter)
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
integrations = append(integrations, createIntegration(t, key))
|
|
}
|
|
|
|
receiver, err = adminClient.Create(ctx, &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: "all-receivers",
|
|
Integrations: integrations,
|
|
},
|
|
}, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, receiver.Spec.Integrations, len(integrations))
|
|
|
|
// Set expected metadata
|
|
receiver.SetAccessControl("canWrite")
|
|
receiver.SetAccessControl("canDelete")
|
|
receiver.SetAccessControl("canReadSecrets")
|
|
receiver.SetAccessControl("canAdmin")
|
|
receiver.SetInUse(0, nil)
|
|
|
|
// Use export endpoint because it's the only way to get decrypted secrets fast.
|
|
cliCfg := helper.Org1.Admin.NewRestConfig()
|
|
legacyCli := alerting.NewAlertingLegacyAPIClient(helper.GetEnv().Server.HTTPServer.Listener.Addr().String(), cliCfg.Username, cliCfg.Password)
|
|
|
|
export := legacyCli.ExportReceiverTyped(t, receiver.Spec.Title, true)
|
|
for _, integration := range export.Receivers {
|
|
expected := notify.AllKnownConfigsForTesting[strings.ToLower(integration.Type)] // to lower because there is LINE that is in different casing in API
|
|
assert.JSONEqf(t, expected.Config, string(integration.Settings), "integration %s", integration.Type)
|
|
}
|
|
})
|
|
|
|
t.Run("should be able read what it is created", func(t *testing.T) {
|
|
get, err := adminClient.Get(ctx, receiver.Name, v1.GetOptions{})
|
|
require.NoError(t, err)
|
|
require.Equal(t, receiver, get)
|
|
t.Run("should return secrets in secureFields but not settings", func(t *testing.T) {
|
|
for _, integration := range get.Spec.Integrations {
|
|
t.Run(integration.Type, func(t *testing.T) {
|
|
secretFields, err := channels_config.GetSecretKeysForContactPointType(integration.Type)
|
|
require.NoError(t, err)
|
|
for _, field := range secretFields {
|
|
assert.Contains(t, integration.SecureFields, field)
|
|
assert.Truef(t, integration.SecureFields[field], "secure field should be always true")
|
|
|
|
value, ok, err := unstructured.NestedString(integration.Settings.Object, strings.Split(field, ".")...)
|
|
assert.NoErrorf(t, err, "failed to read field %s from settings", field)
|
|
assert.Falsef(t, ok, "secret field %s should not be in settings, value [%s]", field, value)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
})
|
|
|
|
t.Run("should fail to persist receiver with invalid config", func(t *testing.T) {
|
|
keysIter := maps.Keys(notify.AllKnownConfigsForTesting)
|
|
keys := slices.Collect(keysIter)
|
|
sort.Strings(keys)
|
|
for _, key := range keys {
|
|
t.Run(key, func(t *testing.T) {
|
|
integration := createIntegration(t, key)
|
|
// Make the integration invalid, so it fails to create. This is usually done by sending empty settings.
|
|
clear(integration.Settings.Object)
|
|
if key == "webex" {
|
|
// Webex integration is special case and passes validation without any settings so we instead set an invalid URL.
|
|
integration.Settings.Set("api_url", "(*^$*^%!@#$*()")
|
|
}
|
|
|
|
receiver, err = adminClient.Create(ctx, &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: fmt.Sprintf("invalid-%s", key),
|
|
Integrations: []v0alpha1.Integration{integration},
|
|
},
|
|
}, v1.CreateOptions{})
|
|
require.Errorf(t, err, "Expected error but got successful result: %v", receiver)
|
|
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest, got: %s", err)
|
|
})
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestIntegrationReceiverListSelector(t *testing.T) {
|
|
if testing.Short() {
|
|
t.Skip("skipping integration test")
|
|
}
|
|
|
|
ctx := context.Background()
|
|
helper := getTestHelper(t)
|
|
|
|
adminK8sClient, err := versioned.NewForConfig(helper.Org1.Admin.NewRestConfig())
|
|
require.NoError(t, err)
|
|
adminClient := adminK8sClient.NotificationsV0alpha1().Receivers("default")
|
|
|
|
require.NoError(t, err)
|
|
recv1 := &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: "test-receiver-1",
|
|
Integrations: []v0alpha1.Integration{
|
|
createIntegration(t, "email"),
|
|
},
|
|
},
|
|
}
|
|
recv1, err = adminClient.Create(ctx, recv1, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
recv2 := &v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: "test-receiver-2",
|
|
Integrations: []v0alpha1.Integration{
|
|
createIntegration(t, "email"),
|
|
},
|
|
},
|
|
}
|
|
recv2, err = adminClient.Create(ctx, recv2, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
env := helper.GetEnv()
|
|
ac := acimpl.ProvideAccessControl(env.FeatureToggles, zanzana.NewNoopClient())
|
|
db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac, bus.ProvideBus(tracing.InitializeTracerForTest()))
|
|
require.NoError(t, err)
|
|
require.NoError(t, db.SetProvenance(ctx, &definitions.EmbeddedContactPoint{
|
|
UID: *recv2.Spec.Integrations[0].Uid,
|
|
}, helper.Org1.Admin.Identity.GetOrgID(), "API"))
|
|
recv2, err = adminClient.Get(ctx, recv2.Name, v1.GetOptions{})
|
|
|
|
require.NoError(t, err)
|
|
|
|
receivers, err := adminClient.List(ctx, v1.ListOptions{})
|
|
require.NoError(t, err)
|
|
require.Len(t, receivers.Items, 3) // Includes default.
|
|
|
|
t.Run("should filter by receiver name", func(t *testing.T) {
|
|
list, err := adminClient.List(ctx, v1.ListOptions{
|
|
FieldSelector: "spec.title=" + recv1.Spec.Title,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1)
|
|
require.Equal(t, recv1.Name, list.Items[0].Name)
|
|
})
|
|
|
|
t.Run("should filter by metadata name", func(t *testing.T) {
|
|
list, err := adminClient.List(ctx, v1.ListOptions{
|
|
FieldSelector: "metadata.name=" + recv2.Name,
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1)
|
|
require.Equal(t, recv2.Name, list.Items[0].Name)
|
|
})
|
|
|
|
t.Run("should filter by multiple filters", func(t *testing.T) {
|
|
list, err := adminClient.List(ctx, v1.ListOptions{
|
|
FieldSelector: fmt.Sprintf("metadata.name=%s,metadata.provenance=%s", recv2.Name, "API"),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Len(t, list.Items, 1)
|
|
require.Equal(t, recv2.Name, list.Items[0].Name)
|
|
})
|
|
|
|
t.Run("should be empty when filter does not match", func(t *testing.T) {
|
|
list, err := adminClient.List(ctx, v1.ListOptions{
|
|
FieldSelector: fmt.Sprintf("metadata.name=%s,metadata.provenance=%s", recv2.Name, "unknown"),
|
|
})
|
|
require.NoError(t, err)
|
|
require.Empty(t, list.Items)
|
|
})
|
|
}
|
|
|
|
// persistInitialConfig helps create an initial config with new receivers using legacy json. Config API blocks receiver
|
|
// modifications, so we need to use k8s API to create new receivers before posting the config.
|
|
func persistInitialConfig(t *testing.T, amConfig definitions.PostableUserConfig, adminClient notificationsv0alpha1.ReceiverInterface, legacyCli alerting.LegacyApiClient) {
|
|
ctx := context.Background()
|
|
|
|
var defaultReceiver *definitions.PostableApiReceiver
|
|
for _, receiver := range amConfig.AlertmanagerConfig.Receivers {
|
|
if receiver.Name == "grafana-default-email" {
|
|
defaultReceiver = receiver
|
|
continue
|
|
}
|
|
|
|
toCreate := v0alpha1.Receiver{
|
|
ObjectMeta: v1.ObjectMeta{
|
|
Namespace: "default",
|
|
},
|
|
Spec: v0alpha1.ReceiverSpec{
|
|
Title: receiver.Name,
|
|
Integrations: []v0alpha1.Integration{},
|
|
},
|
|
}
|
|
|
|
for _, integration := range receiver.GrafanaManagedReceivers {
|
|
settings := common.Unstructured{}
|
|
require.NoError(t, settings.UnmarshalJSON(integration.Settings))
|
|
toCreate.Spec.Integrations = append(toCreate.Spec.Integrations, v0alpha1.Integration{
|
|
Settings: settings,
|
|
Type: integration.Type,
|
|
DisableResolveMessage: util.Pointer(false),
|
|
})
|
|
}
|
|
|
|
created, err := adminClient.Create(ctx, &toCreate, v1.CreateOptions{})
|
|
require.NoError(t, err)
|
|
|
|
for i, integration := range created.Spec.Integrations {
|
|
receiver.GrafanaManagedReceivers[i].UID = *integration.Uid
|
|
}
|
|
}
|
|
|
|
success, err := legacyCli.PostConfiguration(t, amConfig)
|
|
require.Truef(t, success, "Failed to post Alertmanager configuration: %s", err)
|
|
|
|
gettable, status, body := legacyCli.GetAlertmanagerConfigWithStatus(t)
|
|
require.Equalf(t, http.StatusOK, status, body)
|
|
|
|
idx := slices.IndexFunc(gettable.AlertmanagerConfig.Receivers, func(recv *definitions.GettableApiReceiver) bool {
|
|
return recv.Name == "grafana-default-email"
|
|
})
|
|
gettableDefault := gettable.AlertmanagerConfig.Receivers[idx]
|
|
|
|
// Assign uid of default receiver as well.
|
|
defaultReceiver.GrafanaManagedReceivers[0].UID = gettableDefault.GrafanaManagedReceivers[0].UID
|
|
}
|
|
|
|
func createIntegration(t *testing.T, integrationType string) v0alpha1.Integration {
|
|
cfg, ok := notify.AllKnownConfigsForTesting[integrationType]
|
|
require.Truef(t, ok, "no known config for integration type %s", integrationType)
|
|
return createIntegrationWithSettings(t, integrationType, cfg.Config)
|
|
}
|
|
func createIntegrationWithSettings(t *testing.T, integrationType string, settingsJson string) v0alpha1.Integration {
|
|
settings := common.Unstructured{}
|
|
require.NoError(t, settings.UnmarshalJSON([]byte(settingsJson)))
|
|
return v0alpha1.Integration{
|
|
Settings: settings,
|
|
Type: integrationType,
|
|
DisableResolveMessage: util.Pointer(false),
|
|
}
|
|
}
|
|
|
|
func createWildcardPermission(actions ...string) resourcepermissions.SetResourcePermissionCommand {
|
|
return resourcepermissions.SetResourcePermissionCommand{
|
|
Actions: actions,
|
|
Resource: "receivers",
|
|
ResourceAttribute: "uid",
|
|
ResourceID: "*",
|
|
}
|
|
}
|