register endpoint

This commit is contained in:
Yuri Tseretyan
2026-01-05 12:47:21 -05:00
parent d4e94cef50
commit 4236845ea8
5 changed files with 166 additions and 1 deletions
@@ -2,6 +2,8 @@ package app
import (
"context"
"errors"
"fmt"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/logging"
@@ -9,6 +11,7 @@ import (
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1"
)
func New(cfg app.Config) (app.App, error) {
@@ -19,6 +22,14 @@ func New(cfg app.Config) (app.App, error) {
}
}
customCfg, ok := cfg.SpecificConfig.(*Config)
if !ok {
return nil, errors.New("no configuration")
}
if err := customCfg.Validate(); err != nil {
return nil, fmt.Errorf("invalid configuration: %w", err)
}
c := simple.AppConfig{
Name: "alerting.notification",
KubeConfig: cfg.KubeConfig,
@@ -30,6 +41,15 @@ func New(cfg app.Config) (app.App, error) {
},
},
ManagedKinds: managedKinds,
VersionedCustomRoutes: map[string]simple.AppVersionRouteHandlers{
v0alpha1.APIVersion: {
simple.AppVersionRoute{
Namespaced: true,
Path: "testing/integration",
Method: "GET",
}: customCfg.ReceiverTestingHandler.HandleReceiverTestingRequest,
},
},
}
a, err := simple.NewApp(c)
@@ -0,0 +1,23 @@
package app
import (
"context"
"errors"
"github.com/grafana/grafana-app-sdk/app"
)
type Config struct {
ReceiverTestingHandler ReceiverTestingHandler
}
func (c *Config) Validate() error {
if c.ReceiverTestingHandler == nil {
return errors.New("receiver testing handler is required")
}
return nil
}
type ReceiverTestingHandler interface {
HandleReceiverTestingRequest(context.Context, app.CustomRouteResponseWriter, *app.CustomRouteRequest) error
}
@@ -0,0 +1,26 @@
package receivertesting
import (
"context"
"k8s.io/apiserver/pkg/authorization/authorizer"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/services/accesscontrol"
)
func Authorize(ctx context.Context, ac accesscontrol.AccessControl, attr authorizer.Attributes) (authorized authorizer.Decision, reason string, err error) {
user, err := identity.GetRequester(ctx)
if err != nil {
return authorizer.DecisionDeny, "valid user is required", err
}
eval := accesscontrol.EvalAny(
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsWrite),
accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversTest),
)
ok, err := ac.Evaluate(ctx, user, eval)
if ok {
return authorizer.DecisionAllow, "", nil
}
return authorizer.DecisionDeny, "", err
}
@@ -0,0 +1,89 @@
package receivertesting
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/grafana/grafana-app-sdk/app"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alertingnotifications/v0alpha1"
_ "github.com/grafana/grafana/pkg/apimachinery/errutil"
"github.com/grafana/grafana/pkg/apimachinery/identity"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/receiver"
"github.com/grafana/grafana/pkg/services/ngalert"
"github.com/grafana/grafana/pkg/services/ngalert/notifier"
)
type ReceiverTestingHandler struct {
testingSvc *notifier.ReceiverTestingSvc
}
func New(ng *ngalert.AlertNG) *ReceiverTestingHandler {
testingSvc := notifier.NewReceiverTestingSvc(ng.Api.ReceiverService, ng.MultiOrgAlertmanager, ng.SecretsService)
return &ReceiverTestingHandler{
testingSvc: testingSvc,
}
}
func (p *ReceiverTestingHandler) HandleReceiverTestingRequest(ctx context.Context, w app.CustomRouteResponseWriter, r *app.CustomRouteRequest) error {
user, err := identity.GetRequester(ctx)
if err != nil {
return err
}
var req v0alpha1.GetIntegrationTestRequestBody
err = json.NewDecoder(r.Body).Decode(&req)
if err != nil {
writeBadRequest(w, err)
}
alert := notifier.Alert{
Labels: req.Alert.Labels,
Annotations: req.Alert.Annotations,
}
integration, secure, err := receiver.ConvertReceiverIntegrationToIntegration("test-receiver", v0alpha1.ReceiverIntegration(req.Integration))
if err != nil {
writeBadRequest(w, err)
}
receiverUID := ""
if req.ReceiverRef != nil {
receiverUID = *req.ReceiverRef
}
result, err := p.testingSvc.Test(ctx, user, alert, receiverUID, integration, secure)
if err != nil {
// TODO better error handling
writeBadRequest(w, err)
}
response := v0alpha1.GetIntegrationTest{
TypeMeta: metav1.TypeMeta{},
GetIntegrationTestBody: v0alpha1.GetIntegrationTestBody{
Timestamp: time.Time(result.LastNotifyAttempt),
Duration: result.LastNotifyAttemptDuration,
},
}
if result.LastNotifyAttemptError != "" {
response.GetIntegrationTestBody.Error = &result.LastNotifyAttemptError
}
json, err := json.Marshal(response)
if err != nil {
return fmt.Errorf("failed to marshal response: %w", err)
}
w.WriteHeader(200)
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write(json)
return nil
}
func writeBadRequest(w app.CustomRouteResponseWriter, err error) {
w.WriteHeader(400)
_, _ = w.Write([]byte(err.Error()))
}
@@ -15,6 +15,7 @@ import (
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/receiver"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/receiver/receivertesting"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/routingtree"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/templategroup"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/timeinterval"
@@ -53,6 +54,10 @@ func RegisterAppInstaller(
ng: ng,
}
customCfg := notificationsApp.Config{
ReceiverTestingHandler: receivertesting.New(ng),
}
localManifest := apis.LocalManifest()
provider := simple.NewAppProvider(localManifest, nil, notificationsApp.New)
@@ -60,7 +65,7 @@ func RegisterAppInstaller(
appConfig := app.Config{
KubeConfig: restclient.Config{}, // this will be overridden by the installer's InitializeApp method
ManifestData: *localManifest.ManifestData,
SpecificConfig: nil,
SpecificConfig: &customCfg,
}
i, err := appsdkapiserver.NewDefaultAppInstaller(provider, appConfig, &apis.GoTypeAssociator{})
@@ -85,6 +90,8 @@ func (a AlertingNotificationsAppInstaller) GetAuthorizer() authorizer.Authorizer
return receiver.Authorize(ctx, ac.NewReceiverAccess[*ngmodels.Receiver](authz, false), a)
case routingtree.ResourceInfo.GroupResource().Resource:
return routingtree.Authorize(ctx, authz, a)
case "testing":
return receivertesting.Authorize(ctx, authz, a)
}
return authorizer.DecisionNoOpinion, "", nil
})