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
@@ -1,31 +0,0 @@
package receiver
import (
"k8s.io/apiserver/pkg/registry/rest"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
func NewStorage(
legacySvc ReceiverService,
namespacer request.NamespaceMapper,
opts builder.APIGroupOptions,
metadata MetadataService,
) (rest.Storage, error) {
legacyStore := &legacyStorage{
service: legacySvc,
namespacer: namespacer,
tableConverter: ResourceInfo.TableConverter(),
metadata: metadata,
}
if opts.OptsGetter != nil && opts.DualWriteBuilder != nil {
store, err := grafanaregistry.NewRegistryStore(opts.Scheme, ResourceInfo, opts.OptsGetter)
if err != nil {
return nil, err
}
return opts.DualWriteBuilder(ResourceInfo.GroupResource(), legacyStore, store)
}
return legacyStore, nil
}
@@ -1,164 +0,0 @@
package notifications
import (
"context"
"fmt"
"maps"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
resource "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/receiver"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/routingtree"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/templategroup"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/timeinterval"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/ngalert"
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/setting"
)
var _ builder.APIGroupBuilder = (*NotificationsAPIBuilder)(nil)
// This is used just so wire has something unique to return
type NotificationsAPIBuilder struct {
authz accesscontrol.AccessControl
receiverAuth receiver.AccessControlService
ng *ngalert.AlertNG
namespacer request.NamespaceMapper
}
func RegisterAPIService(
features featuremgmt.FeatureToggles,
apiregistration builder.APIRegistrar,
cfg *setting.Cfg,
ng *ngalert.AlertNG,
) *NotificationsAPIBuilder {
if ng.IsDisabled() {
return nil
}
builder := &NotificationsAPIBuilder{
ng: ng,
namespacer: request.GetNamespaceMapper(cfg),
authz: ng.Api.AccessControl,
receiverAuth: ac.NewReceiverAccess[*ngmodels.Receiver](ng.Api.AccessControl, false),
}
apiregistration.RegisterAPI(builder)
return builder
}
func (t *NotificationsAPIBuilder) GetGroupVersion() schema.GroupVersion {
return resource.GroupVersion
}
func (t *NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
if err := receiver.AddKnownTypes(scheme); err != nil {
return err
}
if err := routingtree.AddKnownTypes(scheme); err != nil {
return err
}
if err := templategroup.AddKnownTypes(scheme); err != nil {
return err
}
if err := timeinterval.AddKnownTypes(scheme); err != nil {
return err
}
return nil
}
func (t *NotificationsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error {
addStorage := func(gvr schema.GroupVersionResource, s rest.Storage) {
v, ok := apiGroupInfo.VersionedResourcesStorageMap[gvr.Version]
if !ok {
v = map[string]rest.Storage{}
apiGroupInfo.VersionedResourcesStorageMap[gvr.Version] = v
}
v[gvr.Resource] = s
}
intervals, err := timeinterval.NewStorage(t.ng.Api.MuteTimings, t.namespacer, opts)
if err != nil {
return fmt.Errorf("failed to initialize time-interval storage: %w", err)
}
addStorage(timeinterval.ResourceInfo.GroupVersionResource(), intervals)
recvStorage, err := receiver.NewStorage(t.ng.Api.ReceiverService, t.namespacer, opts, t.ng.Api.ReceiverService)
if err != nil {
return fmt.Errorf("failed to initialize receiver storage: %w", err)
}
addStorage(receiver.ResourceInfo.GroupVersionResource(), recvStorage)
templ, err := templategroup.NewStorage(t.ng.Api.Templates, t.namespacer, opts)
if err != nil {
return fmt.Errorf("failed to initialize templates group storage: %w", err)
}
addStorage(templategroup.ResourceInfo.GroupVersionResource(), templ)
routeStorage, err := routingtree.NewStorage(t.ng.Api.Policies, t.namespacer)
if err != nil {
return fmt.Errorf("failed to initialize route storage: %w", err)
}
addStorage(routingtree.ResourceInfo.GroupVersionResource(), routeStorage)
return nil
}
func (t *NotificationsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
return func(c common.ReferenceCallback) map[string]common.OpenAPIDefinition {
tmpl := templategroup.GetOpenAPIDefinitions(c)
tin := timeinterval.GetOpenAPIDefinitions(c)
recv := receiver.GetOpenAPIDefinitions(c)
rest := routingtree.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
}
}
// PostProcessOpenAPI is a hook to alter OpenAPI3 specification of the API server.
func (t *NotificationsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
// The plugin description
oas.Info.Description = "Grafana Alerting Notification resources"
// The root api URL
root := "/apis/" + t.GetGroupVersion().String() + "/"
// Hide the ability to list or watch across all tenants
delete(oas.Paths.Paths, root+receiver.ResourceInfo.GroupResource().Resource)
delete(oas.Paths.Paths, root+timeinterval.ResourceInfo.GroupResource().Resource)
delete(oas.Paths.Paths, root+templategroup.ResourceInfo.GroupResource().Resource)
delete(oas.Paths.Paths, root+routingtree.ResourceInfo.GroupResource().Resource)
return oas, nil
}
func (t *NotificationsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
return authorizer.AuthorizerFunc(
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
switch a.GetResource() {
case templategroup.ResourceInfo.GroupResource().Resource:
return templategroup.Authorize(ctx, t.authz, a)
case timeinterval.ResourceInfo.GroupResource().Resource:
return timeinterval.Authorize(ctx, t.authz, a)
case receiver.ResourceInfo.GroupResource().Resource:
return receiver.Authorize(ctx, t.receiverAuth, a)
case routingtree.ResourceInfo.GroupResource().Resource:
return routingtree.Authorize(ctx, t.authz, a)
}
return authorizer.DecisionNoOpinion, "", nil
})
}
@@ -1,29 +0,0 @@
package templategroup
import (
"k8s.io/apiserver/pkg/registry/rest"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
func NewStorage(
legacySvc TemplateService,
namespacer request.NamespaceMapper,
opts builder.APIGroupOptions,
) (rest.Storage, error) {
legacyStore := &legacyStorage{
service: legacySvc,
namespacer: namespacer,
tableConverter: ResourceInfo.TableConverter(),
}
if opts.OptsGetter != nil && opts.DualWriteBuilder != nil {
store, err := grafanaregistry.NewRegistryStore(opts.Scheme, ResourceInfo, opts.OptsGetter)
if err != nil {
return nil, err
}
return opts.DualWriteBuilder(ResourceInfo.GroupResource(), legacyStore, store)
}
return legacyStore, nil
}
@@ -1,29 +0,0 @@
package timeinterval
import (
"k8s.io/apiserver/pkg/registry/rest"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
func NewStorage(
legacySvc TimeIntervalService,
namespacer request.NamespaceMapper,
opts builder.APIGroupOptions,
) (rest.Storage, error) {
legacyStore := &legacyStorage{
service: legacySvc,
namespacer: namespacer,
tableConverter: ResourceInfo.TableConverter(),
}
if opts.OptsGetter != nil && opts.DualWriteBuilder != nil {
store, err := grafanaregistry.NewRegistryStore(opts.Scheme, ResourceInfo, opts.OptsGetter)
if err != nil {
return nil, err
}
return opts.DualWriteBuilder(ResourceInfo.GroupResource(), legacyStore, store)
}
return legacyStore, nil
}
-2
View File
@@ -1,7 +1,6 @@
package apiregistry
import (
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications"
dashboardinternal "github.com/grafana/grafana/pkg/registry/apis/dashboard"
"github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot"
"github.com/grafana/grafana/pkg/registry/apis/datasource"
@@ -26,7 +25,6 @@ func ProvideRegistryServiceSink(
_ *folders.FolderAPIBuilder,
_ *iam.IdentityAccessManagementAPIBuilder,
_ *query.QueryAPIBuilder,
_ *notifications.NotificationsAPIBuilder,
_ *userstorage.UserStorageAPIBuilder,
_ *secret.SecretAPIBuilder,
_ *provisioning.APIBuilder,
-2
View File
@@ -3,7 +3,6 @@ package apiregistry
import (
"github.com/google/wire"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications"
dashboardinternal "github.com/grafana/grafana/pkg/registry/apis/dashboard"
"github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot"
"github.com/grafana/grafana/pkg/registry/apis/datasource"
@@ -36,7 +35,6 @@ var WireSet = wire.NewSet(
provisioning.RegisterAPIService,
service.RegisterAPIService,
query.RegisterAPIService,
notifications.RegisterAPIService,
secret.RegisterAPIService,
userstorage.RegisterAPIService,
)
@@ -0,0 +1,19 @@
package receiver
import (
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
func NewStorage(
legacySvc ReceiverService,
namespacer request.NamespaceMapper,
metadata MetadataService,
) grafanarest.Storage {
return &legacyStorage{
service: legacySvc,
namespacer: namespacer,
tableConverter: ResourceInfo.TableConverter(),
metadata: metadata,
}
}
@@ -12,7 +12,6 @@ import (
)
var kind = model.Kind()
var GetOpenAPIDefinitions = model.GetOpenAPIDefinitions
var ResourceInfo = utils.NewResourceInfo(kind.Group(), kind.Version(),
kind.GroupVersionResource().Resource, strings.ToLower(kind.Kind()), kind.Kind(),
func() runtime.Object { return kind.ZeroValue() },
@@ -35,28 +34,3 @@ var ResourceInfo = utils.NewResourceInfo(kind.Group(), kind.Version(),
},
},
)
func AddKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(ResourceInfo.GroupVersion(),
&model.Receiver{},
&model.ReceiverList{},
)
metav1.AddToGroupVersion(scheme, ResourceInfo.GroupVersion())
err := scheme.AddFieldLabelConversionFunc(
ResourceInfo.GroupVersionKind(),
func(label, value string) (string, string, error) {
fieldSet := model.SelectableFields(&model.Receiver{})
for key := range fieldSet {
if label == key {
return label, value, nil
}
}
return "", "", fmt.Errorf("field label not supported for %s: %s", ResourceInfo.GroupVersionKind(), label)
},
)
if err != nil {
return err
}
return nil
}
@@ -0,0 +1,80 @@
package notifications
import (
"context"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/simple"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
notificationsResource "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis"
notificationsApp "github.com/grafana/grafana/apps/alerting/notifications/pkg/app"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/receiver"
"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"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/apiserver/builder/runner"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/ngalert"
ac "github.com/grafana/grafana/pkg/services/ngalert/accesscontrol"
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
"github.com/grafana/grafana/pkg/setting"
)
type AlertingNotificationsAppProvider struct {
app.Provider
}
func RegisterApp(
cfg *setting.Cfg,
ng *ngalert.AlertNG,
) *AlertingNotificationsAppProvider {
if ng.IsDisabled() {
return nil
}
appCfg := &runner.AppBuilderConfig{
Authorizer: getAuthorizer(ng.Api.AccessControl),
LegacyStorageGetter: getLegacyStorage(request.GetNamespaceMapper(cfg), ng),
OpenAPIDefGetter: notificationsResource.GetOpenAPIDefinitions,
ManagedKinds: notificationsResource.GetKinds(),
}
return &AlertingNotificationsAppProvider{
Provider: simple.NewAppProvider(notificationsResource.LocalManifest(), appCfg, notificationsApp.New),
}
}
func getAuthorizer(authz accesscontrol.AccessControl) authorizer.Authorizer {
return authorizer.AuthorizerFunc(
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
switch a.GetResource() {
case templategroup.ResourceInfo.GroupResource().Resource:
return templategroup.Authorize(ctx, authz, a)
case timeinterval.ResourceInfo.GroupResource().Resource:
return timeinterval.Authorize(ctx, authz, a)
case receiver.ResourceInfo.GroupResource().Resource:
return receiver.Authorize(ctx, ac.NewReceiverAccess[*ngmodels.Receiver](authz, false), a)
case routingtree.ResourceInfo.GroupResource().Resource:
return routingtree.Authorize(ctx, authz, a)
}
return authorizer.DecisionNoOpinion, "", nil
})
}
func getLegacyStorage(namespacer request.NamespaceMapper, ng *ngalert.AlertNG) runner.LegacyStorageGetter {
return func(gvr schema.GroupVersionResource) grafanarest.Storage {
if gvr == receiver.ResourceInfo.GroupVersionResource() {
return receiver.NewStorage(ng.Api.ReceiverService, namespacer, ng.Api.ReceiverService)
} else if gvr == timeinterval.ResourceInfo.GroupVersionResource() {
return timeinterval.NewStorage(ng.Api.MuteTimings, namespacer)
} else if gvr == templategroup.ResourceInfo.GroupVersionResource() {
return templategroup.NewStorage(ng.Api.Templates, namespacer)
} else if gvr == routingtree.ResourceInfo.GroupVersionResource() {
return routingtree.NewStorage(ng.Api.Policies, namespacer)
}
panic("unknown legacy storage requested: " + gvr.String())
}
}
@@ -3,15 +3,14 @@ package routingtree
import (
"k8s.io/apiserver/pkg/registry/rest"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
func NewStorage(legacySvc RouteService, namespacer request.NamespaceMapper) (rest.Storage, error) {
legacyStore := &legacyStorage{
func NewStorage(legacySvc RouteService, namespacer request.NamespaceMapper) grafanarest.Storage {
return &legacyStorage{
service: legacySvc,
namespacer: namespacer,
tableConverter: rest.NewDefaultTableConvertor(ResourceInfo.GroupResource()),
}
// TODO implement dual write for routes. This API is a special beast - the resource is singleton.
return legacyStore, nil
}
@@ -3,7 +3,6 @@ package routingtree
import (
"strings"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
model "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1"
@@ -11,19 +10,9 @@ import (
)
var kind = model.Kind()
var GetOpenAPIDefinitions = model.GetOpenAPIDefinitions
var ResourceInfo = utils.NewResourceInfo(kind.Group(), kind.Version(),
kind.GroupVersionResource().Resource, strings.ToLower(kind.Kind()), kind.Kind(),
func() runtime.Object { return kind.ZeroValue() },
func() runtime.Object { return kind.ZeroListValue() },
utils.TableColumns{},
)
func AddKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(ResourceInfo.GroupVersion(),
&model.RoutingTree{},
&model.RoutingTreeList{},
)
metav1.AddToGroupVersion(scheme, ResourceInfo.GroupVersion())
return nil
}
@@ -0,0 +1,17 @@
package templategroup
import (
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
func NewStorage(
legacySvc TemplateService,
namespacer request.NamespaceMapper,
) grafanarest.Storage {
return &legacyStorage{
service: legacySvc,
namespacer: namespacer,
tableConverter: ResourceInfo.TableConverter(),
}
}
@@ -12,7 +12,6 @@ import (
)
var kind = model.Kind()
var GetOpenAPIDefinitions = model.GetOpenAPIDefinitions
var ResourceInfo = utils.NewResourceInfo(kind.Group(), kind.Version(),
kind.GroupVersionResource().Resource, strings.ToLower(kind.Kind()), kind.Kind(),
func() runtime.Object { return kind.ZeroValue() },
@@ -32,28 +31,3 @@ var ResourceInfo = utils.NewResourceInfo(kind.Group(), kind.Version(),
},
},
)
func AddKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(ResourceInfo.GroupVersion(),
&model.TemplateGroup{},
&model.TemplateGroupList{},
)
metav1.AddToGroupVersion(scheme, ResourceInfo.GroupVersion())
err := scheme.AddFieldLabelConversionFunc(
ResourceInfo.GroupVersionKind(),
func(label, value string) (string, string, error) {
fieldSet := model.SelectableFields(&model.TemplateGroup{})
for key := range fieldSet {
if label == key {
return label, value, nil
}
}
return "", "", fmt.Errorf("field label not supported for %s: %s", ResourceInfo.GroupVersionKind(), label)
},
)
if err != nil {
return err
}
return nil
}
@@ -0,0 +1,17 @@
package timeinterval
import (
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
)
func NewStorage(
legacySvc TimeIntervalService,
namespacer request.NamespaceMapper,
) grafanarest.Storage {
return &legacyStorage{
service: legacySvc,
namespacer: namespacer,
tableConverter: ResourceInfo.TableConverter(),
}
}
@@ -12,7 +12,6 @@ import (
)
var kind = model.Kind()
var GetOpenAPIDefinitions = model.GetOpenAPIDefinitions
var ResourceInfo = utils.NewResourceInfo(kind.Group(), kind.Version(),
kind.GroupVersionResource().Resource, strings.ToLower(kind.Kind()), kind.Kind(),
func() runtime.Object { return kind.ZeroValue() },
@@ -32,28 +31,3 @@ var ResourceInfo = utils.NewResourceInfo(kind.Group(), kind.Version(),
},
},
)
func AddKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(ResourceInfo.GroupVersion(),
&model.TimeInterval{},
&model.TimeIntervalList{},
)
metav1.AddToGroupVersion(scheme, ResourceInfo.GroupVersion())
err := scheme.AddFieldLabelConversionFunc(
ResourceInfo.GroupVersionKind(),
func(label, value string) (string, string, error) {
fieldSet := model.SelectableFields(&model.TimeInterval{})
for key := range fieldSet {
if label == key {
return label, value, nil
}
}
return "", "", fmt.Errorf("field label not supported for %s: %s", ResourceInfo.GroupVersionKind(), label)
},
)
if err != nil {
return err
}
return nil
}
+7 -1
View File
@@ -5,9 +5,12 @@ import (
"slices"
"github.com/grafana/grafana-app-sdk/app"
"k8s.io/client-go/rest"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/registry"
"github.com/grafana/grafana/pkg/registry/apps/advisor"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications"
"github.com/grafana/grafana/pkg/registry/apps/investigations"
"github.com/grafana/grafana/pkg/registry/apps/playlist"
"github.com/grafana/grafana/pkg/services/apiserver"
@@ -15,7 +18,6 @@ import (
"github.com/grafana/grafana/pkg/services/apiserver/builder/runner"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"k8s.io/client-go/rest"
)
var (
@@ -35,6 +37,7 @@ func ProvideRegistryServiceSink(
playlistAppProvider *playlist.PlaylistAppProvider,
investigationAppProvider *investigations.InvestigationsAppProvider,
advisorAppProvider *advisor.AdvisorAppProvider,
alertingNotificationsAppProvider *notifications.AlertingNotificationsAppProvider,
grafanaCfg *setting.Cfg,
) (*Service, error) {
cfgWrapper := func(ctx context.Context) (*rest.Config, error) {
@@ -62,6 +65,9 @@ func ProvideRegistryServiceSink(
!slices.Contains(grafanaCfg.DisablePlugins, "grafana-advisor-app") {
providers = append(providers, advisorAppProvider)
}
if alertingNotificationsAppProvider != nil {
providers = append(providers, alertingNotificationsAppProvider)
}
apiGroupRunner, err = runner.NewAPIGroupRunner(cfg, providers...)
if err != nil {
+2
View File
@@ -5,6 +5,7 @@ import (
"github.com/grafana/grafana/apps/advisor/pkg/app/checkregistry"
"github.com/grafana/grafana/pkg/registry/apps/advisor"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications"
"github.com/grafana/grafana/pkg/registry/apps/investigations"
"github.com/grafana/grafana/pkg/registry/apps/playlist"
)
@@ -15,5 +16,6 @@ var WireSet = wire.NewSet(
investigations.RegisterApp,
advisor.RegisterApp,
checkregistry.ProvideService,
notifications.RegisterApp,
wire.Bind(new(checkregistry.CheckService), new(*checkregistry.Service)),
)
@@ -1,6 +1,8 @@
package runner
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
@@ -10,6 +12,7 @@ import (
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana/pkg/apimachinery/utils"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
@@ -74,6 +77,29 @@ func (b *appBuilder) InstallSchema(scheme *runtime.Scheme) error {
}
scheme.AddKnownTypeWithName(gvInternal.WithKind(kind.Kind()), kind.ZeroValue())
scheme.AddKnownTypeWithName(gvInternal.WithKind(kind.Kind()+"List"), kind.ZeroListValue())
if len(kind.SelectableFields()) == 0 {
continue
}
gvk := gv.WithKind(kind.Kind())
err := scheme.AddFieldLabelConversionFunc(
gvk,
func(label, value string) (string, string, error) {
if label == "metadata.name" || label == "metadata.namespace" {
return label, value, nil
}
fields := kind.SelectableFields()
for _, field := range fields {
if field.FieldSelector == label {
return label, value, nil
}
}
return "", "", fmt.Errorf("field label not supported for %s: %s", gvk, label)
},
)
if err != nil {
return err
}
}
}
return scheme.SetVersionPriority(gv)
@@ -24,7 +24,7 @@ import (
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/receiver/v0alpha1"
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/routingtree"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/routingtree"
test_common "github.com/grafana/grafana/pkg/tests/apis/alerting/notifications/common"
@@ -18,7 +18,7 @@ import (
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/routingtree/v0alpha1"
v0alpha1_timeinterval "github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/routingtree"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/routingtree"
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1/fakes"
"github.com/grafana/grafana/pkg/bus"
@@ -21,8 +21,8 @@ import (
"github.com/grafana/grafana/apps/alerting/notifications/pkg/apis/timeinterval/v0alpha1/fakes"
"github.com/grafana/grafana/pkg/bus"
"github.com/grafana/grafana/pkg/infra/tracing"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/routingtree"
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/timeinterval"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/routingtree"
"github.com/grafana/grafana/pkg/registry/apps/alerting/notifications/timeinterval"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
@@ -1,7 +1,6 @@
{
"openapi": "3.0.0",
"info": {
"description": "Grafana Alerting Notification resources",
"title": "notifications.alerting.grafana.app/v0alpha1"
},
"paths": {
@@ -3434,6 +3433,11 @@
}
},
"x-kubernetes-group-version-kind": [
{
"group": "notifications.alerting.grafana.app",
"kind": "Receiver",
"version": "__internal"
},
{
"group": "notifications.alerting.grafana.app",
"kind": "Receiver",
@@ -3477,6 +3481,11 @@
}
},
"x-kubernetes-group-version-kind": [
{
"group": "notifications.alerting.grafana.app",
"kind": "ReceiverList",
"version": "__internal"
},
{
"group": "notifications.alerting.grafana.app",
"kind": "ReceiverList",
@@ -3715,6 +3724,11 @@
}
},
"x-kubernetes-group-version-kind": [
{
"group": "notifications.alerting.grafana.app",
"kind": "RoutingTree",
"version": "__internal"
},
{
"group": "notifications.alerting.grafana.app",
"kind": "RoutingTree",
@@ -3758,6 +3772,11 @@
}
},
"x-kubernetes-group-version-kind": [
{
"group": "notifications.alerting.grafana.app",
"kind": "RoutingTreeList",
"version": "__internal"
},
{
"group": "notifications.alerting.grafana.app",
"kind": "RoutingTreeList",
@@ -3961,6 +3980,11 @@
}
},
"x-kubernetes-group-version-kind": [
{
"group": "notifications.alerting.grafana.app",
"kind": "TemplateGroup",
"version": "__internal"
},
{
"group": "notifications.alerting.grafana.app",
"kind": "TemplateGroup",
@@ -4004,6 +4028,11 @@
}
},
"x-kubernetes-group-version-kind": [
{
"group": "notifications.alerting.grafana.app",
"kind": "TemplateGroupList",
"version": "__internal"
},
{
"group": "notifications.alerting.grafana.app",
"kind": "TemplateGroupList",
@@ -4179,6 +4208,11 @@
}
},
"x-kubernetes-group-version-kind": [
{
"group": "notifications.alerting.grafana.app",
"kind": "TimeInterval",
"version": "__internal"
},
{
"group": "notifications.alerting.grafana.app",
"kind": "TimeInterval",
@@ -4222,6 +4256,11 @@
}
},
"x-kubernetes-group-version-kind": [
{
"group": "notifications.alerting.grafana.app",
"kind": "TimeIntervalList",
"version": "__internal"
},
{
"group": "notifications.alerting.grafana.app",
"kind": "TimeIntervalList",