mirror of
https://github.com/grafana/grafana.git
synced 2024-11-25 10:20:29 -06:00
Alerting: Add field selectors for k8s receivers API (#93015)
Add field selectors for k8s receivers API metadata.provenance spec.title
This commit is contained in:
parent
f64b121ddb
commit
eea28172e0
@ -106,6 +106,22 @@ func AddKnownTypesGroup(scheme *runtime.Scheme, g schema.GroupVersion) error {
|
||||
return err
|
||||
}
|
||||
|
||||
err = scheme.AddFieldLabelConversionFunc(
|
||||
ReceiverResourceInfo.GroupVersionKind(),
|
||||
func(label, value string) (string, string, error) {
|
||||
fieldSet := SelectableReceiverFields(&Receiver{})
|
||||
for key := range fieldSet {
|
||||
if label == key {
|
||||
return label, value, nil
|
||||
}
|
||||
}
|
||||
return "", "", fmt.Errorf("field label not supported for %s: %s", scope.ScopeNodeResourceInfo.GroupVersionKind(), label)
|
||||
},
|
||||
)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -119,6 +135,16 @@ func SelectableTimeIntervalsFields(obj *TimeInterval) fields.Set {
|
||||
})
|
||||
}
|
||||
|
||||
func SelectableReceiverFields(obj *Receiver) fields.Set {
|
||||
if obj == nil {
|
||||
return nil
|
||||
}
|
||||
return generic.MergeFieldsSets(generic.ObjectMetaFieldsSet(&obj.ObjectMeta, false), fields.Set{
|
||||
"metadata.provenance": obj.GetProvenanceStatus(),
|
||||
"spec.title": obj.Spec.Title,
|
||||
})
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
|
@ -4,6 +4,7 @@ import (
|
||||
"maps"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
@ -13,7 +14,7 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/notifier/legacy_storage"
|
||||
)
|
||||
|
||||
func convertToK8sResources(orgID int64, receivers []*ngmodels.Receiver, namespacer request.NamespaceMapper) (*model.ReceiverList, error) {
|
||||
func convertToK8sResources(orgID int64, receivers []*ngmodels.Receiver, namespacer request.NamespaceMapper, selector fields.Selector) (*model.ReceiverList, error) {
|
||||
result := &model.ReceiverList{
|
||||
Items: make([]model.Receiver, 0, len(receivers)),
|
||||
}
|
||||
@ -22,6 +23,9 @@ func convertToK8sResources(orgID int64, receivers []*ngmodels.Receiver, namespac
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if selector != nil && !selector.Empty() && !selector.Matches(model.SelectableReceiverFields(k8sResource)) {
|
||||
continue
|
||||
}
|
||||
result.Items = append(result.Items, *k8sResource)
|
||||
}
|
||||
return result, nil
|
||||
|
@ -61,7 +61,7 @@ func (s *legacyStorage) ConvertToTable(ctx context.Context, object runtime.Objec
|
||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) List(ctx context.Context, _ *internalversion.ListOptions) (runtime.Object, error) {
|
||||
func (s *legacyStorage) List(ctx context.Context, opts *internalversion.ListOptions) (runtime.Object, error) {
|
||||
orgId, err := request.OrgIDForList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -85,7 +85,7 @@ func (s *legacyStorage) List(ctx context.Context, _ *internalversion.ListOptions
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return convertToK8sResources(orgId, res, s.namespacer)
|
||||
return convertToK8sResources(orgId, res, s.namespacer, opts.FieldSelector)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOptions) (runtime.Object, error) {
|
||||
|
@ -1,11 +1,17 @@
|
||||
package receiver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
apistore "k8s.io/apiserver/pkg/storage"
|
||||
|
||||
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
@ -39,7 +45,7 @@ func NewStorage(
|
||||
s := &genericregistry.Store{
|
||||
NewFunc: resourceInfo.NewFunc,
|
||||
NewListFunc: resourceInfo.NewListFunc,
|
||||
PredicateFunc: grafanaregistry.Matcher,
|
||||
PredicateFunc: Matcher,
|
||||
DefaultQualifiedResource: resourceInfo.GroupResource(),
|
||||
SingularQualifiedResource: resourceInfo.SingularGroupResource(),
|
||||
TableConvertor: legacyStore.tableConverter,
|
||||
@ -55,3 +61,19 @@ func NewStorage(
|
||||
}
|
||||
return legacyStore, nil
|
||||
}
|
||||
|
||||
func GetAttrs(obj runtime.Object) (labels.Set, fields.Set, error) {
|
||||
if s, ok := obj.(*model.Receiver); ok {
|
||||
return s.Labels, model.SelectableReceiverFields(s), nil
|
||||
}
|
||||
return nil, nil, fmt.Errorf("object of type %T is not supported", obj)
|
||||
}
|
||||
|
||||
// Matcher returns a generic.SelectionPredicate that matches on label and field selectors.
|
||||
func Matcher(label labels.Selector, field fields.Selector) apistore.SelectionPredicate {
|
||||
return apistore.SelectionPredicate{
|
||||
Label: label,
|
||||
Field: field,
|
||||
GetAttrs: GetAttrs,
|
||||
}
|
||||
}
|
||||
|
@ -934,6 +934,98 @@ func TestIntegrationCRUD(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
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)
|
||||
|
Loading…
Reference in New Issue
Block a user