Alerting: Migrate notifications API to app-platform SDK application (#104424)

* introduce alerting notification app
* move code as is and remove from old registry
* update api server registration
* update make file and remove unnecessary args, copy some useful make commands from dashboards
* update codeowners

* move constants inside module and remove dependency from grafana
* add support for selectors to the app builder
This commit is contained in:
Yuri Tseretyan
2025-04-30 10:23:56 -04:00
committed by GitHub
parent 454e260207
commit 85344e30c0
50 changed files with 449 additions and 413 deletions
@@ -0,0 +1,5 @@
package v0alpha1
const InternalPrefix = "grafana.com/"
const ProvenanceStatusAnnotationKey = InternalPrefix + "provenance"
const ProvenanceStatusNone = "none"
@@ -11,6 +11,8 @@ import (
"github.com/grafana/grafana-app-sdk/app"
)
var ()
var appManifestData = app.ManifestData{
AppName: "alerting",
Group: "notifications.alerting.grafana.app",
@@ -6,16 +6,16 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apiserver/pkg/registry/generic"
"github.com/grafana/grafana/apps/alerting/common"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alerting/v0alpha1"
)
func (o *Receiver) GetProvenanceStatus() string {
if o == nil || o.Annotations == nil {
return ""
}
s, ok := o.Annotations[common.ProvenanceStatusAnnotationKey]
s, ok := o.Annotations[v0alpha1.ProvenanceStatusAnnotationKey]
if !ok || s == "" {
return common.ProvenanceStatusNone
return v0alpha1.ProvenanceStatusNone
}
return s
}
@@ -25,9 +25,9 @@ func (o *Receiver) SetProvenanceStatus(status string) {
o.Annotations = make(map[string]string, 1)
}
if status == "" {
status = common.ProvenanceStatusNone
status = v0alpha1.ProvenanceStatusNone
}
o.Annotations[common.ProvenanceStatusAnnotationKey] = status
o.Annotations[v0alpha1.ProvenanceStatusAnnotationKey] = status
}
func (o *Receiver) SetAccessControl(action string) {
@@ -40,7 +40,7 @@ func (o *Receiver) SetAccessControl(action string) {
// AccessControlAnnotation returns the key for the access control annotation for the given action.
// Ex. grafana.com/access/canDelete.
func AccessControlAnnotation(action string) string {
return fmt.Sprintf("%s%s/%s", common.InternalPrefix, "access", action)
return fmt.Sprintf("%s%s/%s", v0alpha1.InternalPrefix, "access", action)
}
func (o *Receiver) SetInUse(routesCnt int, rules []string) {
@@ -54,7 +54,7 @@ func (o *Receiver) SetInUse(routesCnt int, rules []string) {
// 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", common.InternalPrefix, "inUse", resource)
return fmt.Sprintf("%s%s/%s", v0alpha1.InternalPrefix, "inUse", resource)
}
func SelectableFields(obj *Receiver) fields.Set {
@@ -13,7 +13,7 @@ import (
// schema is unexported to prevent accidental overwrites
var (
schemaReceiver = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &Receiver{}, &ReceiverList{}, resource.WithKind("Receiver"),
resource.WithPlural("receivers"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{
resource.WithPlural("receivers"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{
FieldSelector: "spec.title",
FieldValueFunc: func(o resource.Object) (string, error) {
cast, ok := o.(*Receiver)
@@ -1,7 +1,7 @@
package v0alpha1
import (
"github.com/grafana/grafana/apps/alerting/common"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alerting/v0alpha1"
)
const UserDefinedRoutingTreeName = "user-defined"
@@ -10,9 +10,9 @@ func (o *RoutingTree) GetProvenanceStatus() string {
if o == nil || o.Annotations == nil {
return ""
}
s, ok := o.Annotations[common.ProvenanceStatusAnnotationKey]
s, ok := o.Annotations[v0alpha1.ProvenanceStatusAnnotationKey]
if !ok || s == "" {
return common.ProvenanceStatusNone
return v0alpha1.ProvenanceStatusNone
}
return s
}
@@ -22,7 +22,7 @@ func (o *RoutingTree) SetProvenanceStatus(status string) {
o.Annotations = make(map[string]string, 1)
}
if status == "" {
status = common.ProvenanceStatusNone
status = v0alpha1.ProvenanceStatusNone
}
o.Annotations[common.ProvenanceStatusAnnotationKey] = status
o.Annotations[v0alpha1.ProvenanceStatusAnnotationKey] = status
}
@@ -4,7 +4,7 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apiserver/pkg/registry/generic"
"github.com/grafana/grafana/apps/alerting/common"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alerting/v0alpha1"
)
const DefaultTemplateTitle = "Built-in Templates"
@@ -13,9 +13,9 @@ func (o *TemplateGroup) GetProvenanceStatus() string {
if o == nil || o.Annotations == nil {
return ""
}
s, ok := o.Annotations[common.ProvenanceStatusAnnotationKey]
s, ok := o.Annotations[v0alpha1.ProvenanceStatusAnnotationKey]
if !ok || s == "" {
return common.ProvenanceStatusNone
return v0alpha1.ProvenanceStatusNone
}
return s
}
@@ -25,9 +25,9 @@ func (o *TemplateGroup) SetProvenanceStatus(status string) {
o.Annotations = make(map[string]string, 1)
}
if status == "" {
status = common.ProvenanceStatusNone
status = v0alpha1.ProvenanceStatusNone
}
o.Annotations[common.ProvenanceStatusAnnotationKey] = status
o.Annotations[v0alpha1.ProvenanceStatusAnnotationKey] = status
}
func SelectableFields(obj *TemplateGroup) fields.Set {
@@ -13,7 +13,7 @@ import (
// schema is unexported to prevent accidental overwrites
var (
schemaTemplateGroup = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &TemplateGroup{}, &TemplateGroupList{}, resource.WithKind("TemplateGroup"),
resource.WithPlural("templategroups"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{
resource.WithPlural("templategroups"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{
FieldSelector: "spec.title",
FieldValueFunc: func(o resource.Object) (string, error) {
cast, ok := o.(*TemplateGroup)
@@ -4,16 +4,16 @@ import (
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apiserver/pkg/registry/generic"
"github.com/grafana/grafana/apps/alerting/common"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alerting/v0alpha1"
)
func (o *TimeInterval) GetProvenanceStatus() string {
if o == nil || o.Annotations == nil {
return ""
}
s, ok := o.Annotations[common.ProvenanceStatusAnnotationKey]
s, ok := o.Annotations[v0alpha1.ProvenanceStatusAnnotationKey]
if !ok || s == "" {
return common.ProvenanceStatusNone
return v0alpha1.ProvenanceStatusNone
}
return s
}
@@ -23,9 +23,9 @@ func (o *TimeInterval) SetProvenanceStatus(status string) {
o.Annotations = make(map[string]string, 1)
}
if status == "" {
status = common.ProvenanceStatusNone
status = v0alpha1.ProvenanceStatusNone
}
o.Annotations[common.ProvenanceStatusAnnotationKey] = status
o.Annotations[v0alpha1.ProvenanceStatusAnnotationKey] = status
}
func SelectableFields(obj *TimeInterval) fields.Set {
@@ -8,9 +8,10 @@ import (
"time"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1"
"github.com/grafana/grafana/pkg/util"
)
var UTCLocation = "UTC"
// +k8s:openapi-gen=false
type IntervalMutator func(spec *v0alpha1.Interval)
@@ -62,7 +63,7 @@ func (t IntervalGenerator) generateLocation() *string {
if rand.Int()%3 == 0 {
return nil
}
return util.Pointer("UTC")
return &UTCLocation
}
func (t IntervalGenerator) generateMonth() string {
@@ -13,7 +13,7 @@ import (
// schema is unexported to prevent accidental overwrites
var (
schemaTimeInterval = resource.NewSimpleSchema("notifications.alerting.grafana.app", "v0alpha1", &TimeInterval{}, &TimeIntervalList{}, resource.WithKind("TimeInterval"),
resource.WithPlural("timeintervals"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{{
resource.WithPlural("timeintervals"), resource.WithScope(resource.NamespacedScope), resource.WithSelectableFields([]resource.SelectableField{resource.SelectableField{
FieldSelector: "spec.name",
FieldValueFunc: func(o resource.Object) (string, error) {
cast, ok := o.(*TimeInterval)
+37 -2
View File
@@ -1,5 +1,40 @@
package apis
import "k8s.io/apimachinery/pkg/runtime/schema"
import (
"maps"
var GroupVersion = schema.GroupVersion{Group: "notifications.alerting.grafana.app", Version: "v0alpha1"}
sdkResource "github.com/grafana/grafana-app-sdk/resource"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/common"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/alerting/v0alpha1"
receiverv0alpha1 "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/receiver/v0alpha1"
routingtreev0alpha1 "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1"
templategroupv0alpha1 "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/templategroup/v0alpha1"
timeintervalv0alpha1 "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1"
)
func GetOpenAPIDefinitions(c common.ReferenceCallback) map[string]common.OpenAPIDefinition {
tmpl := templategroupv0alpha1.GetOpenAPIDefinitions(c)
tin := timeintervalv0alpha1.GetOpenAPIDefinitions(c)
recv := receiverv0alpha1.GetOpenAPIDefinitions(c)
rest := routingtreev0alpha1.GetOpenAPIDefinitions(c)
result := make(map[string]common.OpenAPIDefinition, len(tmpl)+len(tin)+len(recv)+len(rest))
maps.Copy(result, tmpl)
maps.Copy(result, tin)
maps.Copy(result, recv)
maps.Copy(result, rest)
return result
}
func GetKinds() map[schema.GroupVersion][]sdkResource.Kind {
result := map[schema.GroupVersion][]sdkResource.Kind{
v0alpha1.GroupVersion: {
receiverv0alpha1.Kind(),
routingtreev0alpha1.Kind(),
templategroupv0alpha1.Kind(),
timeintervalv0alpha1.Kind(),
},
}
return result
}
@@ -0,0 +1,43 @@
package app
import (
"context"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/logging"
"github.com/grafana/grafana-app-sdk/simple"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis"
)
func New(cfg app.Config) (app.App, error) {
managedKinds := make([]simple.AppManagedKind, 0)
for _, kinds := range apis.GetKinds() {
for _, kind := range kinds {
managedKinds = append(managedKinds, simple.AppManagedKind{Kind: kind})
}
}
c := simple.AppConfig{
Name: "alerting.notification",
KubeConfig: cfg.KubeConfig,
InformerConfig: simple.AppInformerConfig{
ErrorHandler: func(ctx context.Context, err error) {
logging.DefaultLogger.With("error", err).Error("Informer processing error")
},
},
ManagedKinds: managedKinds,
}
a, err := simple.NewApp(c)
if err != nil {
return nil, err
}
err = a.ValidateManifest(cfg.ManifestData)
if err != nil {
return nil, err
}
return a, nil
}