mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Alerting: Notifications Routes API (#91550)
* Introduce new models RoutingTree, RouteDefaults and Route and api-server to serve them that is backed by provisioning notification policy service. * update method UpdatePolicyTree of notification policy service to return route and new version * declare new actions alert.notifications.routes:read and alert.notifications.routes:write and two corresponding fixed roles. --------- Co-authored-by: Tom Ratcliffe <tom.ratcliffe@grafana.com> Co-authored-by: Matthew Jacobson <matthew.jacobson@grafana.com>
This commit is contained in:
4
apps/alerting/notifications/Makefile
Normal file
4
apps/alerting/notifications/Makefile
Normal file
@@ -0,0 +1,4 @@
|
||||
.PHONY: generate
|
||||
generate:
|
||||
## --crdencoding none is needed to avoid infinite loop while generating recursive models'
|
||||
grafana-app-sdk generate -c . -g ./apis --crdencoding none
|
||||
49
apps/alerting/notifications/routingtree.cue
Normal file
49
apps/alerting/notifications/routingtree.cue
Normal file
@@ -0,0 +1,49 @@
|
||||
package core
|
||||
|
||||
route: {
|
||||
kind: "RoutingTree"
|
||||
group: "notifications"
|
||||
apiResource: {
|
||||
groupOverride: "notifications.alerting.grafana.app"
|
||||
}
|
||||
codegen: {
|
||||
frontend: false
|
||||
backend: true
|
||||
}
|
||||
pluralName: "RoutingTrees"
|
||||
current: "v0alpha1"
|
||||
versions: {
|
||||
"v0alpha1": {
|
||||
schema: {
|
||||
#RouteDefaults: {
|
||||
receiver: string
|
||||
group_by?: [...string]
|
||||
group_wait?: string
|
||||
group_interval?: string
|
||||
repeat_interval?: string
|
||||
}
|
||||
#Matcher: {
|
||||
type: "=" |"!="|"=~"|"!~" @cuetsy(kind="enum")
|
||||
label: string
|
||||
value: string
|
||||
}
|
||||
#Route: {
|
||||
receiver?: string
|
||||
matchers?: [...#Matcher]
|
||||
continue: bool
|
||||
|
||||
group_by?: [...string]
|
||||
mute_time_intervals?: [...string]
|
||||
routes?: [...#Route]
|
||||
group_wait?: string
|
||||
group_interval?: string
|
||||
repeat_interval?: string
|
||||
}
|
||||
spec: {
|
||||
defaults: #RouteDefaults
|
||||
routes: [...#Route]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,10 @@ func init() {
|
||||
}
|
||||
|
||||
const (
|
||||
GROUP = "notifications.alerting.grafana.app"
|
||||
VERSION = "v0alpha1"
|
||||
APIVERSION = GROUP + "/" + VERSION
|
||||
GROUP = "notifications.alerting.grafana.app"
|
||||
VERSION = "v0alpha1"
|
||||
APIVERSION = GROUP + "/" + VERSION
|
||||
UserDefinedRoutingTreeName = "user-defined"
|
||||
)
|
||||
|
||||
var (
|
||||
@@ -86,6 +87,27 @@ var (
|
||||
},
|
||||
},
|
||||
)
|
||||
RouteResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
|
||||
"routingtrees", "routingtree", "RoutingTree",
|
||||
func() runtime.Object { return &RoutingTree{} },
|
||||
func() runtime.Object { return &RoutingTreeList{} },
|
||||
utils.TableColumns{
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
// {Name: "Intervals", Type: "string", Format: "string", Description: "The display name"},
|
||||
},
|
||||
Reader: func(obj any) ([]interface{}, error) {
|
||||
r, ok := obj.(*RoutingTree)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected resource or info")
|
||||
}
|
||||
return []interface{}{
|
||||
r.Name,
|
||||
// r.Spec, //TODO implement formatting for Spec, same as UI?
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
)
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
|
||||
// SchemaBuilder is used by standard codegen
|
||||
@@ -108,6 +130,8 @@ func AddKnownTypesGroup(scheme *runtime.Scheme, g schema.GroupVersion) error {
|
||||
&ReceiverList{},
|
||||
&TemplateGroup{},
|
||||
&TemplateGroupList{},
|
||||
&RoutingTree{},
|
||||
&RoutingTreeList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, g)
|
||||
|
||||
|
||||
52
pkg/apis/alerting_notifications/v0alpha1/routingtree_spec.go
Normal file
52
pkg/apis/alerting_notifications/v0alpha1/routingtree_spec.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package v0alpha1
|
||||
|
||||
// Defines values for MatcherType.
|
||||
const (
|
||||
MatcherTypeNotEqual MatcherType = "!="
|
||||
MatcherTypeEqual MatcherType = "="
|
||||
MatcherTypeEqualRegex MatcherType = "=~"
|
||||
MatcherTypeNotEqualRegex MatcherType = "!~"
|
||||
)
|
||||
|
||||
// Matcher defines model for Matcher.
|
||||
// +k8s:openapi-gen=true
|
||||
type Matcher struct {
|
||||
Label string `json:"label"`
|
||||
Type MatcherType `json:"type"`
|
||||
Value string `json:"value"`
|
||||
}
|
||||
|
||||
// MatcherType defines model for Matcher.Type.
|
||||
// +k8s:openapi-gen=true
|
||||
type MatcherType string
|
||||
|
||||
// Route defines model for Route.
|
||||
// +k8s:openapi-gen=true
|
||||
type Route struct {
|
||||
Continue bool `json:"continue,omitempty"`
|
||||
GroupBy []string `json:"group_by,omitempty"`
|
||||
GroupInterval *string `json:"group_interval,omitempty"`
|
||||
GroupWait *string `json:"group_wait,omitempty"`
|
||||
Matchers []Matcher `json:"matchers,omitempty"`
|
||||
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
|
||||
Receiver *string `json:"receiver,omitempty"`
|
||||
RepeatInterval *string `json:"repeat_interval,omitempty"`
|
||||
Routes []Route `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
// RouteDefaults defines model for RouteDefaults.
|
||||
// +k8s:openapi-gen=true
|
||||
type RouteDefaults struct {
|
||||
GroupBy []string `json:"group_by,omitempty"`
|
||||
GroupInterval *string `json:"group_interval,omitempty"`
|
||||
GroupWait *string `json:"group_wait,omitempty"`
|
||||
Receiver string `json:"receiver"`
|
||||
RepeatInterval *string `json:"repeat_interval,omitempty"`
|
||||
}
|
||||
|
||||
// Spec defines model for Spec.
|
||||
// +k8s:openapi-gen=true
|
||||
type RoutingTreeSpec struct {
|
||||
Defaults RouteDefaults `json:"defaults"`
|
||||
Routes []Route `json:"routes"`
|
||||
}
|
||||
@@ -255,3 +255,86 @@ type TemplateGroupList struct {
|
||||
}
|
||||
|
||||
// endregion
|
||||
|
||||
// region Routes
|
||||
|
||||
// +genclient
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:openapi-gen=true
|
||||
type RoutingTree struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
Spec RoutingTreeSpec `json:"spec"`
|
||||
}
|
||||
|
||||
func (o *RoutingTree) GetSpec() any {
|
||||
return o.Spec
|
||||
}
|
||||
|
||||
func (o *RoutingTree) SetSpec(spec any) error {
|
||||
cast, ok := spec.(RoutingTreeSpec)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||
}
|
||||
o.Spec = cast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *RoutingTree) GetCreatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||
}
|
||||
|
||||
func (o *RoutingTree) SetCreatedBy(createdBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||
}
|
||||
|
||||
func (o *RoutingTree) GetUpdateTimestamp() time.Time {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
parsed, _ := time.Parse(time.RFC3339, o.ObjectMeta.Annotations["grafana.com/updateTimestamp"])
|
||||
return parsed
|
||||
}
|
||||
|
||||
func (o *RoutingTree) SetUpdateTimestamp(updateTimestamp time.Time) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updateTimestamp"] = updateTimestamp.Format(time.RFC3339)
|
||||
}
|
||||
|
||||
func (o *RoutingTree) GetUpdatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||
}
|
||||
|
||||
func (o *RoutingTree) SetUpdatedBy(updatedBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
// +k8s:openapi-gen=true
|
||||
type RoutingTreeList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
Items []RoutingTree `json:"items"`
|
||||
}
|
||||
|
||||
// endregion Routes
|
||||
|
||||
@@ -97,3 +97,24 @@ func (o *TemplateGroup) SetProvenanceStatus(status string) {
|
||||
}
|
||||
o.Annotations[ProvenanceStatusAnnotationKey] = status
|
||||
}
|
||||
|
||||
func (o *RoutingTree) GetProvenanceStatus() string {
|
||||
if o == nil || o.Annotations == nil {
|
||||
return ""
|
||||
}
|
||||
s, ok := o.Annotations[ProvenanceStatusAnnotationKey]
|
||||
if !ok || s == "" {
|
||||
return ProvenanceStatusNone
|
||||
}
|
||||
return s
|
||||
}
|
||||
|
||||
func (o *RoutingTree) SetProvenanceStatus(status string) {
|
||||
if o.Annotations == nil {
|
||||
o.Annotations = make(map[string]string, 1)
|
||||
}
|
||||
if status == "" {
|
||||
status = ProvenanceStatusNone
|
||||
}
|
||||
o.Annotations[ProvenanceStatusAnnotationKey] = status
|
||||
}
|
||||
|
||||
@@ -91,6 +91,22 @@ func (in *Interval) DeepCopy() *Interval {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Matcher) DeepCopyInto(out *Matcher) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Matcher.
|
||||
func (in *Matcher) DeepCopy() *Matcher {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Matcher)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Receiver) DeepCopyInto(out *Receiver) {
|
||||
*out = *in
|
||||
@@ -174,6 +190,184 @@ func (in *ReceiverSpec) DeepCopy() *ReceiverSpec {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Route) DeepCopyInto(out *Route) {
|
||||
*out = *in
|
||||
if in.GroupBy != nil {
|
||||
in, out := &in.GroupBy, &out.GroupBy
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.GroupInterval != nil {
|
||||
in, out := &in.GroupInterval, &out.GroupInterval
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.GroupWait != nil {
|
||||
in, out := &in.GroupWait, &out.GroupWait
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Matchers != nil {
|
||||
in, out := &in.Matchers, &out.Matchers
|
||||
*out = make([]Matcher, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.MuteTimeIntervals != nil {
|
||||
in, out := &in.MuteTimeIntervals, &out.MuteTimeIntervals
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Receiver != nil {
|
||||
in, out := &in.Receiver, &out.Receiver
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.RepeatInterval != nil {
|
||||
in, out := &in.RepeatInterval, &out.RepeatInterval
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Routes != nil {
|
||||
in, out := &in.Routes, &out.Routes
|
||||
*out = make([]Route, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Route.
|
||||
func (in *Route) DeepCopy() *Route {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Route)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RouteDefaults) DeepCopyInto(out *RouteDefaults) {
|
||||
*out = *in
|
||||
if in.GroupBy != nil {
|
||||
in, out := &in.GroupBy, &out.GroupBy
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.GroupInterval != nil {
|
||||
in, out := &in.GroupInterval, &out.GroupInterval
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.GroupWait != nil {
|
||||
in, out := &in.GroupWait, &out.GroupWait
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.RepeatInterval != nil {
|
||||
in, out := &in.RepeatInterval, &out.RepeatInterval
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RouteDefaults.
|
||||
func (in *RouteDefaults) DeepCopy() *RouteDefaults {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RouteDefaults)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RoutingTree) DeepCopyInto(out *RoutingTree) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoutingTree.
|
||||
func (in *RoutingTree) DeepCopy() *RoutingTree {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RoutingTree)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *RoutingTree) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RoutingTreeList) DeepCopyInto(out *RoutingTreeList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]RoutingTree, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoutingTreeList.
|
||||
func (in *RoutingTreeList) DeepCopy() *RoutingTreeList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RoutingTreeList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *RoutingTreeList) DeepCopyObject() runtime.Object {
|
||||
if c := in.DeepCopy(); c != nil {
|
||||
return c
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *RoutingTreeSpec) DeepCopyInto(out *RoutingTreeSpec) {
|
||||
*out = *in
|
||||
in.Defaults.DeepCopyInto(&out.Defaults)
|
||||
if in.Routes != nil {
|
||||
in, out := &in.Routes, &out.Routes
|
||||
*out = make([]Route, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new RoutingTreeSpec.
|
||||
func (in *RoutingTreeSpec) DeepCopy() *RoutingTreeSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(RoutingTreeSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TemplateGroup) DeepCopyInto(out *TemplateGroup) {
|
||||
*out = *in
|
||||
|
||||
@@ -16,9 +16,15 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
return map[string]common.OpenAPIDefinition{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Integration": schema_pkg_apis_alerting_notifications_v0alpha1_Integration(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Interval": schema_pkg_apis_alerting_notifications_v0alpha1_Interval(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Matcher": schema_pkg_apis_alerting_notifications_v0alpha1_Matcher(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Receiver": schema_pkg_apis_alerting_notifications_v0alpha1_Receiver(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.ReceiverList": schema_pkg_apis_alerting_notifications_v0alpha1_ReceiverList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.ReceiverSpec": schema_pkg_apis_alerting_notifications_v0alpha1_ReceiverSpec(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Route": schema_pkg_apis_alerting_notifications_v0alpha1_Route(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RouteDefaults": schema_pkg_apis_alerting_notifications_v0alpha1_RouteDefaults(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RoutingTree": schema_pkg_apis_alerting_notifications_v0alpha1_RoutingTree(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RoutingTreeList": schema_pkg_apis_alerting_notifications_v0alpha1_RoutingTreeList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RoutingTreeSpec": schema_pkg_apis_alerting_notifications_v0alpha1_RoutingTreeSpec(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TemplateGroup": schema_pkg_apis_alerting_notifications_v0alpha1_TemplateGroup(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TemplateGroupList": schema_pkg_apis_alerting_notifications_v0alpha1_TemplateGroupList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TemplateGroupSpec": schema_pkg_apis_alerting_notifications_v0alpha1_TemplateGroupSpec(ref),
|
||||
@@ -209,6 +215,41 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_Interval(ref common.Referen
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_Matcher(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Matcher defines model for Matcher.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"label": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"type": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"value": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"label", "type", "value"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_Receiver(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@@ -339,6 +380,282 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_ReceiverSpec(ref common.Ref
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_Route(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Route defines model for Route.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"continue": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"group_by": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"group_interval": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"group_wait": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"matchers": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Matcher"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"mute_time_intervals": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"receiver": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"repeat_interval": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"routes": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Route"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Matcher", "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Route"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_RouteDefaults(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "RouteDefaults defines model for RouteDefaults.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"group_by": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"group_interval": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"group_wait": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"receiver": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"repeat_interval": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"receiver"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_RoutingTree(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
"spec": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RoutingTreeSpec"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "spec"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RoutingTreeSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_RoutingTreeList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RoutingTree"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RoutingTree", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_RoutingTreeSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Spec defines model for Spec.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"defaults": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RouteDefaults"),
|
||||
},
|
||||
},
|
||||
"routes": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Route"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"defaults", "routes"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Route", "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.RouteDefaults"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_TemplateGroup(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
||||
@@ -1,4 +1,19 @@
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,GroupBy
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,Matchers
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,MuteTimeIntervals
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,Routes
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,RouteDefaults,GroupBy
|
||||
API rule violation: list_type_missing,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,RoutingTreeSpec,Routes
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Interval,DaysOfMonth
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,GroupBy
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,GroupInterval
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,GroupWait
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,MuteTimeIntervals
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Route,RepeatInterval
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,RouteDefaults,GroupBy
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,RouteDefaults,GroupInterval
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,RouteDefaults,GroupWait
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,RouteDefaults,RepeatInterval
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,TimeIntervalSpec,TimeIntervals
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,TimeRange,EndTime
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,TimeRange,StartTime
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
)
|
||||
|
||||
// MatcherApplyConfiguration represents a declarative configuration of the Matcher type for use
|
||||
// with apply.
|
||||
type MatcherApplyConfiguration struct {
|
||||
Label *string `json:"label,omitempty"`
|
||||
Type *v0alpha1.MatcherType `json:"type,omitempty"`
|
||||
Value *string `json:"value,omitempty"`
|
||||
}
|
||||
|
||||
// MatcherApplyConfiguration constructs a declarative configuration of the Matcher type for use with
|
||||
// apply.
|
||||
func Matcher() *MatcherApplyConfiguration {
|
||||
return &MatcherApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithLabel sets the Label field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Label field is set to the value of the last call.
|
||||
func (b *MatcherApplyConfiguration) WithLabel(value string) *MatcherApplyConfiguration {
|
||||
b.Label = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithType sets the Type field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Type field is set to the value of the last call.
|
||||
func (b *MatcherApplyConfiguration) WithType(value v0alpha1.MatcherType) *MatcherApplyConfiguration {
|
||||
b.Type = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithValue sets the Value field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Value field is set to the value of the last call.
|
||||
func (b *MatcherApplyConfiguration) WithValue(value string) *MatcherApplyConfiguration {
|
||||
b.Value = &value
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// RouteApplyConfiguration represents a declarative configuration of the Route type for use
|
||||
// with apply.
|
||||
type RouteApplyConfiguration struct {
|
||||
Continue *bool `json:"continue,omitempty"`
|
||||
GroupBy []string `json:"group_by,omitempty"`
|
||||
GroupInterval *string `json:"group_interval,omitempty"`
|
||||
GroupWait *string `json:"group_wait,omitempty"`
|
||||
Matchers []MatcherApplyConfiguration `json:"matchers,omitempty"`
|
||||
MuteTimeIntervals []string `json:"mute_time_intervals,omitempty"`
|
||||
Receiver *string `json:"receiver,omitempty"`
|
||||
RepeatInterval *string `json:"repeat_interval,omitempty"`
|
||||
Routes []RouteApplyConfiguration `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
// RouteApplyConfiguration constructs a declarative configuration of the Route type for use with
|
||||
// apply.
|
||||
func Route() *RouteApplyConfiguration {
|
||||
return &RouteApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithContinue sets the Continue field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Continue field is set to the value of the last call.
|
||||
func (b *RouteApplyConfiguration) WithContinue(value bool) *RouteApplyConfiguration {
|
||||
b.Continue = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGroupBy adds the given value to the GroupBy field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the GroupBy field.
|
||||
func (b *RouteApplyConfiguration) WithGroupBy(values ...string) *RouteApplyConfiguration {
|
||||
for i := range values {
|
||||
b.GroupBy = append(b.GroupBy, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGroupInterval sets the GroupInterval field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the GroupInterval field is set to the value of the last call.
|
||||
func (b *RouteApplyConfiguration) WithGroupInterval(value string) *RouteApplyConfiguration {
|
||||
b.GroupInterval = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGroupWait sets the GroupWait field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the GroupWait field is set to the value of the last call.
|
||||
func (b *RouteApplyConfiguration) WithGroupWait(value string) *RouteApplyConfiguration {
|
||||
b.GroupWait = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMatchers adds the given value to the Matchers field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Matchers field.
|
||||
func (b *RouteApplyConfiguration) WithMatchers(values ...*MatcherApplyConfiguration) *RouteApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithMatchers")
|
||||
}
|
||||
b.Matchers = append(b.Matchers, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMuteTimeIntervals adds the given value to the MuteTimeIntervals field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the MuteTimeIntervals field.
|
||||
func (b *RouteApplyConfiguration) WithMuteTimeIntervals(values ...string) *RouteApplyConfiguration {
|
||||
for i := range values {
|
||||
b.MuteTimeIntervals = append(b.MuteTimeIntervals, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithReceiver sets the Receiver field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Receiver field is set to the value of the last call.
|
||||
func (b *RouteApplyConfiguration) WithReceiver(value string) *RouteApplyConfiguration {
|
||||
b.Receiver = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithRepeatInterval sets the RepeatInterval field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the RepeatInterval field is set to the value of the last call.
|
||||
func (b *RouteApplyConfiguration) WithRepeatInterval(value string) *RouteApplyConfiguration {
|
||||
b.RepeatInterval = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithRoutes adds the given value to the Routes field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Routes field.
|
||||
func (b *RouteApplyConfiguration) WithRoutes(values ...*RouteApplyConfiguration) *RouteApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithRoutes")
|
||||
}
|
||||
b.Routes = append(b.Routes, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// RouteDefaultsApplyConfiguration represents a declarative configuration of the RouteDefaults type for use
|
||||
// with apply.
|
||||
type RouteDefaultsApplyConfiguration struct {
|
||||
GroupBy []string `json:"group_by,omitempty"`
|
||||
GroupInterval *string `json:"group_interval,omitempty"`
|
||||
GroupWait *string `json:"group_wait,omitempty"`
|
||||
Receiver *string `json:"receiver,omitempty"`
|
||||
RepeatInterval *string `json:"repeat_interval,omitempty"`
|
||||
}
|
||||
|
||||
// RouteDefaultsApplyConfiguration constructs a declarative configuration of the RouteDefaults type for use with
|
||||
// apply.
|
||||
func RouteDefaults() *RouteDefaultsApplyConfiguration {
|
||||
return &RouteDefaultsApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithGroupBy adds the given value to the GroupBy field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the GroupBy field.
|
||||
func (b *RouteDefaultsApplyConfiguration) WithGroupBy(values ...string) *RouteDefaultsApplyConfiguration {
|
||||
for i := range values {
|
||||
b.GroupBy = append(b.GroupBy, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGroupInterval sets the GroupInterval field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the GroupInterval field is set to the value of the last call.
|
||||
func (b *RouteDefaultsApplyConfiguration) WithGroupInterval(value string) *RouteDefaultsApplyConfiguration {
|
||||
b.GroupInterval = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGroupWait sets the GroupWait field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the GroupWait field is set to the value of the last call.
|
||||
func (b *RouteDefaultsApplyConfiguration) WithGroupWait(value string) *RouteDefaultsApplyConfiguration {
|
||||
b.GroupWait = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithReceiver sets the Receiver field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Receiver field is set to the value of the last call.
|
||||
func (b *RouteDefaultsApplyConfiguration) WithReceiver(value string) *RouteDefaultsApplyConfiguration {
|
||||
b.Receiver = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithRepeatInterval sets the RepeatInterval field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the RepeatInterval field is set to the value of the last call.
|
||||
func (b *RouteDefaultsApplyConfiguration) WithRepeatInterval(value string) *RouteDefaultsApplyConfiguration {
|
||||
b.RepeatInterval = &value
|
||||
return b
|
||||
}
|
||||
@@ -0,0 +1,202 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
v1 "k8s.io/client-go/applyconfigurations/meta/v1"
|
||||
)
|
||||
|
||||
// RoutingTreeApplyConfiguration represents a declarative configuration of the RoutingTree type for use
|
||||
// with apply.
|
||||
type RoutingTreeApplyConfiguration struct {
|
||||
v1.TypeMetaApplyConfiguration `json:",inline"`
|
||||
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
|
||||
Spec *RoutingTreeSpecApplyConfiguration `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// RoutingTree constructs a declarative configuration of the RoutingTree type for use with
|
||||
// apply.
|
||||
func RoutingTree(name, namespace string) *RoutingTreeApplyConfiguration {
|
||||
b := &RoutingTreeApplyConfiguration{}
|
||||
b.WithName(name)
|
||||
b.WithNamespace(namespace)
|
||||
b.WithKind("RoutingTree")
|
||||
b.WithAPIVersion("notifications.alerting.grafana.app/v0alpha1")
|
||||
return b
|
||||
}
|
||||
|
||||
// WithKind sets the Kind field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Kind field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithKind(value string) *RoutingTreeApplyConfiguration {
|
||||
b.Kind = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAPIVersion sets the APIVersion field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the APIVersion field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithAPIVersion(value string) *RoutingTreeApplyConfiguration {
|
||||
b.APIVersion = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithName sets the Name field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Name field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithName(value string) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.Name = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGenerateName sets the GenerateName field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the GenerateName field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithGenerateName(value string) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.GenerateName = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithNamespace sets the Namespace field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Namespace field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithNamespace(value string) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.Namespace = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithUID sets the UID field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the UID field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithUID(value types.UID) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.UID = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithResourceVersion sets the ResourceVersion field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the ResourceVersion field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithResourceVersion(value string) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.ResourceVersion = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithGeneration sets the Generation field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Generation field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithGeneration(value int64) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.Generation = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithCreationTimestamp sets the CreationTimestamp field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the CreationTimestamp field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithCreationTimestamp(value metav1.Time) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.CreationTimestamp = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithDeletionTimestamp sets the DeletionTimestamp field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the DeletionTimestamp field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.DeletionTimestamp = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithDeletionGracePeriodSeconds sets the DeletionGracePeriodSeconds field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the DeletionGracePeriodSeconds field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
b.DeletionGracePeriodSeconds = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLabels puts the entries into the Labels field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, the entries provided by each call will be put on the Labels field,
|
||||
// overwriting an existing map entries in Labels field with the same key.
|
||||
func (b *RoutingTreeApplyConfiguration) WithLabels(entries map[string]string) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
if b.Labels == nil && len(entries) > 0 {
|
||||
b.Labels = make(map[string]string, len(entries))
|
||||
}
|
||||
for k, v := range entries {
|
||||
b.Labels[k] = v
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithAnnotations puts the entries into the Annotations field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, the entries provided by each call will be put on the Annotations field,
|
||||
// overwriting an existing map entries in Annotations field with the same key.
|
||||
func (b *RoutingTreeApplyConfiguration) WithAnnotations(entries map[string]string) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
if b.Annotations == nil && len(entries) > 0 {
|
||||
b.Annotations = make(map[string]string, len(entries))
|
||||
}
|
||||
for k, v := range entries {
|
||||
b.Annotations[k] = v
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithOwnerReferences adds the given value to the OwnerReferences field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the OwnerReferences field.
|
||||
func (b *RoutingTreeApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithOwnerReferences")
|
||||
}
|
||||
b.OwnerReferences = append(b.OwnerReferences, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithFinalizers adds the given value to the Finalizers field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Finalizers field.
|
||||
func (b *RoutingTreeApplyConfiguration) WithFinalizers(values ...string) *RoutingTreeApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
for i := range values {
|
||||
b.Finalizers = append(b.Finalizers, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *RoutingTreeApplyConfiguration) ensureObjectMetaApplyConfigurationExists() {
|
||||
if b.ObjectMetaApplyConfiguration == nil {
|
||||
b.ObjectMetaApplyConfiguration = &v1.ObjectMetaApplyConfiguration{}
|
||||
}
|
||||
}
|
||||
|
||||
// WithSpec sets the Spec field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Spec field is set to the value of the last call.
|
||||
func (b *RoutingTreeApplyConfiguration) WithSpec(value *RoutingTreeSpecApplyConfiguration) *RoutingTreeApplyConfiguration {
|
||||
b.Spec = value
|
||||
return b
|
||||
}
|
||||
|
||||
// GetName retrieves the value of the Name field in the declarative configuration.
|
||||
func (b *RoutingTreeApplyConfiguration) GetName() *string {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
return b.Name
|
||||
}
|
||||
@@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// RoutingTreeSpecApplyConfiguration represents a declarative configuration of the RoutingTreeSpec type for use
|
||||
// with apply.
|
||||
type RoutingTreeSpecApplyConfiguration struct {
|
||||
Defaults *RouteDefaultsApplyConfiguration `json:"defaults,omitempty"`
|
||||
Routes []RouteApplyConfiguration `json:"routes,omitempty"`
|
||||
}
|
||||
|
||||
// RoutingTreeSpecApplyConfiguration constructs a declarative configuration of the RoutingTreeSpec type for use with
|
||||
// apply.
|
||||
func RoutingTreeSpec() *RoutingTreeSpecApplyConfiguration {
|
||||
return &RoutingTreeSpecApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithDefaults sets the Defaults field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Defaults field is set to the value of the last call.
|
||||
func (b *RoutingTreeSpecApplyConfiguration) WithDefaults(value *RouteDefaultsApplyConfiguration) *RoutingTreeSpecApplyConfiguration {
|
||||
b.Defaults = value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithRoutes adds the given value to the Routes field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the Routes field.
|
||||
func (b *RoutingTreeSpecApplyConfiguration) WithRoutes(values ...*RouteApplyConfiguration) *RoutingTreeSpecApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithRoutes")
|
||||
}
|
||||
b.Routes = append(b.Routes, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
@@ -24,10 +24,20 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &alertingnotificationsv0alpha1.IntegrationApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("Interval"):
|
||||
return &alertingnotificationsv0alpha1.IntervalApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("Matcher"):
|
||||
return &alertingnotificationsv0alpha1.MatcherApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("Receiver"):
|
||||
return &alertingnotificationsv0alpha1.ReceiverApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("ReceiverSpec"):
|
||||
return &alertingnotificationsv0alpha1.ReceiverSpecApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("Route"):
|
||||
return &alertingnotificationsv0alpha1.RouteApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("RouteDefaults"):
|
||||
return &alertingnotificationsv0alpha1.RouteDefaultsApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("RoutingTree"):
|
||||
return &alertingnotificationsv0alpha1.RoutingTreeApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("RoutingTreeSpec"):
|
||||
return &alertingnotificationsv0alpha1.RoutingTreeSpecApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("TemplateGroup"):
|
||||
return &alertingnotificationsv0alpha1.TemplateGroupApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("TemplateGroupSpec"):
|
||||
|
||||
@@ -15,6 +15,7 @@ import (
|
||||
type NotificationsV0alpha1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
ReceiversGetter
|
||||
RoutingTreesGetter
|
||||
TemplateGroupsGetter
|
||||
TimeIntervalsGetter
|
||||
}
|
||||
@@ -28,6 +29,10 @@ func (c *NotificationsV0alpha1Client) Receivers(namespace string) ReceiverInterf
|
||||
return newReceivers(c, namespace)
|
||||
}
|
||||
|
||||
func (c *NotificationsV0alpha1Client) RoutingTrees(namespace string) RoutingTreeInterface {
|
||||
return newRoutingTrees(c, namespace)
|
||||
}
|
||||
|
||||
func (c *NotificationsV0alpha1Client) TemplateGroups(namespace string) TemplateGroupInterface {
|
||||
return newTemplateGroups(c, namespace)
|
||||
}
|
||||
|
||||
@@ -18,6 +18,10 @@ func (c *FakeNotificationsV0alpha1) Receivers(namespace string) v0alpha1.Receive
|
||||
return &FakeReceivers{c, namespace}
|
||||
}
|
||||
|
||||
func (c *FakeNotificationsV0alpha1) RoutingTrees(namespace string) v0alpha1.RoutingTreeInterface {
|
||||
return &FakeRoutingTrees{c, namespace}
|
||||
}
|
||||
|
||||
func (c *FakeNotificationsV0alpha1) TemplateGroups(namespace string) v0alpha1.TemplateGroupInterface {
|
||||
return &FakeTemplateGroups{c, namespace}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
"context"
|
||||
json "encoding/json"
|
||||
"fmt"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
alertingnotificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
labels "k8s.io/apimachinery/pkg/labels"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakeRoutingTrees implements RoutingTreeInterface
|
||||
type FakeRoutingTrees struct {
|
||||
Fake *FakeNotificationsV0alpha1
|
||||
ns string
|
||||
}
|
||||
|
||||
var routingtreesResource = v0alpha1.SchemeGroupVersion.WithResource("routingtrees")
|
||||
|
||||
var routingtreesKind = v0alpha1.SchemeGroupVersion.WithKind("RoutingTree")
|
||||
|
||||
// Get takes name of the routingTree, and returns the corresponding routingTree object, and an error if there is any.
|
||||
func (c *FakeRoutingTrees) Get(ctx context.Context, name string, options v1.GetOptions) (result *v0alpha1.RoutingTree, err error) {
|
||||
emptyResult := &v0alpha1.RoutingTree{}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewGetActionWithOptions(routingtreesResource, c.ns, name, options), emptyResult)
|
||||
|
||||
if obj == nil {
|
||||
return emptyResult, err
|
||||
}
|
||||
return obj.(*v0alpha1.RoutingTree), err
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of RoutingTrees that match those selectors.
|
||||
func (c *FakeRoutingTrees) List(ctx context.Context, opts v1.ListOptions) (result *v0alpha1.RoutingTreeList, err error) {
|
||||
emptyResult := &v0alpha1.RoutingTreeList{}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewListActionWithOptions(routingtreesResource, routingtreesKind, c.ns, opts), emptyResult)
|
||||
|
||||
if obj == nil {
|
||||
return emptyResult, err
|
||||
}
|
||||
|
||||
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||
if label == nil {
|
||||
label = labels.Everything()
|
||||
}
|
||||
list := &v0alpha1.RoutingTreeList{ListMeta: obj.(*v0alpha1.RoutingTreeList).ListMeta}
|
||||
for _, item := range obj.(*v0alpha1.RoutingTreeList).Items {
|
||||
if label.Matches(labels.Set(item.Labels)) {
|
||||
list.Items = append(list.Items, item)
|
||||
}
|
||||
}
|
||||
return list, err
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested routingTrees.
|
||||
func (c *FakeRoutingTrees) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
|
||||
return c.Fake.
|
||||
InvokesWatch(testing.NewWatchActionWithOptions(routingtreesResource, c.ns, opts))
|
||||
|
||||
}
|
||||
|
||||
// Create takes the representation of a routingTree and creates it. Returns the server's representation of the routingTree, and an error, if there is any.
|
||||
func (c *FakeRoutingTrees) Create(ctx context.Context, routingTree *v0alpha1.RoutingTree, opts v1.CreateOptions) (result *v0alpha1.RoutingTree, err error) {
|
||||
emptyResult := &v0alpha1.RoutingTree{}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewCreateActionWithOptions(routingtreesResource, c.ns, routingTree, opts), emptyResult)
|
||||
|
||||
if obj == nil {
|
||||
return emptyResult, err
|
||||
}
|
||||
return obj.(*v0alpha1.RoutingTree), err
|
||||
}
|
||||
|
||||
// Update takes the representation of a routingTree and updates it. Returns the server's representation of the routingTree, and an error, if there is any.
|
||||
func (c *FakeRoutingTrees) Update(ctx context.Context, routingTree *v0alpha1.RoutingTree, opts v1.UpdateOptions) (result *v0alpha1.RoutingTree, err error) {
|
||||
emptyResult := &v0alpha1.RoutingTree{}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateActionWithOptions(routingtreesResource, c.ns, routingTree, opts), emptyResult)
|
||||
|
||||
if obj == nil {
|
||||
return emptyResult, err
|
||||
}
|
||||
return obj.(*v0alpha1.RoutingTree), err
|
||||
}
|
||||
|
||||
// Delete takes name of the routingTree and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeRoutingTrees) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteActionWithOptions(routingtreesResource, c.ns, name, opts), &v0alpha1.RoutingTree{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *FakeRoutingTrees) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
|
||||
action := testing.NewDeleteCollectionActionWithOptions(routingtreesResource, c.ns, opts, listOpts)
|
||||
|
||||
_, err := c.Fake.Invokes(action, &v0alpha1.RoutingTreeList{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched routingTree.
|
||||
func (c *FakeRoutingTrees) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v0alpha1.RoutingTree, err error) {
|
||||
emptyResult := &v0alpha1.RoutingTree{}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceActionWithOptions(routingtreesResource, c.ns, name, pt, data, opts, subresources...), emptyResult)
|
||||
|
||||
if obj == nil {
|
||||
return emptyResult, err
|
||||
}
|
||||
return obj.(*v0alpha1.RoutingTree), err
|
||||
}
|
||||
|
||||
// Apply takes the given apply declarative configuration, applies it and returns the applied routingTree.
|
||||
func (c *FakeRoutingTrees) Apply(ctx context.Context, routingTree *alertingnotificationsv0alpha1.RoutingTreeApplyConfiguration, opts v1.ApplyOptions) (result *v0alpha1.RoutingTree, err error) {
|
||||
if routingTree == nil {
|
||||
return nil, fmt.Errorf("routingTree provided to Apply must not be nil")
|
||||
}
|
||||
data, err := json.Marshal(routingTree)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := routingTree.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("routingTree.Name must be provided to Apply")
|
||||
}
|
||||
emptyResult := &v0alpha1.RoutingTree{}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceActionWithOptions(routingtreesResource, c.ns, *name, types.ApplyPatchType, data, opts.ToPatchOptions()), emptyResult)
|
||||
|
||||
if obj == nil {
|
||||
return emptyResult, err
|
||||
}
|
||||
return obj.(*v0alpha1.RoutingTree), err
|
||||
}
|
||||
@@ -6,6 +6,8 @@ package v0alpha1
|
||||
|
||||
type ReceiverExpansion interface{}
|
||||
|
||||
type RoutingTreeExpansion interface{}
|
||||
|
||||
type TemplateGroupExpansion interface{}
|
||||
|
||||
type TimeIntervalExpansion interface{}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
alertingnotificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1"
|
||||
scheme "github.com/grafana/grafana/pkg/generated/clientset/versioned/scheme"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
types "k8s.io/apimachinery/pkg/types"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
gentype "k8s.io/client-go/gentype"
|
||||
)
|
||||
|
||||
// RoutingTreesGetter has a method to return a RoutingTreeInterface.
|
||||
// A group's client should implement this interface.
|
||||
type RoutingTreesGetter interface {
|
||||
RoutingTrees(namespace string) RoutingTreeInterface
|
||||
}
|
||||
|
||||
// RoutingTreeInterface has methods to work with RoutingTree resources.
|
||||
type RoutingTreeInterface interface {
|
||||
Create(ctx context.Context, routingTree *v0alpha1.RoutingTree, opts v1.CreateOptions) (*v0alpha1.RoutingTree, error)
|
||||
Update(ctx context.Context, routingTree *v0alpha1.RoutingTree, opts v1.UpdateOptions) (*v0alpha1.RoutingTree, error)
|
||||
Delete(ctx context.Context, name string, opts v1.DeleteOptions) error
|
||||
DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error
|
||||
Get(ctx context.Context, name string, opts v1.GetOptions) (*v0alpha1.RoutingTree, error)
|
||||
List(ctx context.Context, opts v1.ListOptions) (*v0alpha1.RoutingTreeList, error)
|
||||
Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error)
|
||||
Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v0alpha1.RoutingTree, err error)
|
||||
Apply(ctx context.Context, routingTree *alertingnotificationsv0alpha1.RoutingTreeApplyConfiguration, opts v1.ApplyOptions) (result *v0alpha1.RoutingTree, err error)
|
||||
RoutingTreeExpansion
|
||||
}
|
||||
|
||||
// routingTrees implements RoutingTreeInterface
|
||||
type routingTrees struct {
|
||||
*gentype.ClientWithListAndApply[*v0alpha1.RoutingTree, *v0alpha1.RoutingTreeList, *alertingnotificationsv0alpha1.RoutingTreeApplyConfiguration]
|
||||
}
|
||||
|
||||
// newRoutingTrees returns a RoutingTrees
|
||||
func newRoutingTrees(c *NotificationsV0alpha1Client, namespace string) *routingTrees {
|
||||
return &routingTrees{
|
||||
gentype.NewClientWithListAndApply[*v0alpha1.RoutingTree, *v0alpha1.RoutingTreeList, *alertingnotificationsv0alpha1.RoutingTreeApplyConfiguration](
|
||||
"routingtrees",
|
||||
c.RESTClient(),
|
||||
scheme.ParameterCodec,
|
||||
namespace,
|
||||
func() *v0alpha1.RoutingTree { return &v0alpha1.RoutingTree{} },
|
||||
func() *v0alpha1.RoutingTreeList { return &v0alpha1.RoutingTreeList{} }),
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,8 @@ import (
|
||||
type Interface interface {
|
||||
// Receivers returns a ReceiverInformer.
|
||||
Receivers() ReceiverInformer
|
||||
// RoutingTrees returns a RoutingTreeInformer.
|
||||
RoutingTrees() RoutingTreeInformer
|
||||
// TemplateGroups returns a TemplateGroupInformer.
|
||||
TemplateGroups() TemplateGroupInformer
|
||||
// TimeIntervals returns a TimeIntervalInformer.
|
||||
@@ -34,6 +36,11 @@ func (v *version) Receivers() ReceiverInformer {
|
||||
return &receiverInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
|
||||
// RoutingTrees returns a RoutingTreeInformer.
|
||||
func (v *version) RoutingTrees() RoutingTreeInformer {
|
||||
return &routingTreeInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
}
|
||||
|
||||
// TemplateGroups returns a TemplateGroupInformer.
|
||||
func (v *version) TemplateGroups() TemplateGroupInformer {
|
||||
return &templateGroupInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions}
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
time "time"
|
||||
|
||||
alertingnotificationsv0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
versioned "github.com/grafana/grafana/pkg/generated/clientset/versioned"
|
||||
internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces"
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/generated/listers/alerting_notifications/v0alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
watch "k8s.io/apimachinery/pkg/watch"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// RoutingTreeInformer provides access to a shared informer and lister for
|
||||
// RoutingTrees.
|
||||
type RoutingTreeInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() v0alpha1.RoutingTreeLister
|
||||
}
|
||||
|
||||
type routingTreeInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewRoutingTreeInformer constructs a new informer for RoutingTree type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewRoutingTreeInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredRoutingTreeInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredRoutingTreeInformer constructs a new informer for RoutingTree type.
|
||||
// Always prefer using an informer factory to get a shared informer instead of getting an independent
|
||||
// one. This reduces memory footprint and number of connections to the server.
|
||||
func NewFilteredRoutingTreeInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers, tweakListOptions internalinterfaces.TweakListOptionsFunc) cache.SharedIndexInformer {
|
||||
return cache.NewSharedIndexInformer(
|
||||
&cache.ListWatch{
|
||||
ListFunc: func(options v1.ListOptions) (runtime.Object, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.NotificationsV0alpha1().RoutingTrees(namespace).List(context.TODO(), options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.NotificationsV0alpha1().RoutingTrees(namespace).Watch(context.TODO(), options)
|
||||
},
|
||||
},
|
||||
&alertingnotificationsv0alpha1.RoutingTree{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *routingTreeInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredRoutingTreeInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *routingTreeInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&alertingnotificationsv0alpha1.RoutingTree{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *routingTreeInformer) Lister() v0alpha1.RoutingTreeLister {
|
||||
return v0alpha1.NewRoutingTreeLister(f.Informer().GetIndexer())
|
||||
}
|
||||
@@ -42,6 +42,8 @@ func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource
|
||||
// Group=notifications.alerting.grafana.app, Version=v0alpha1
|
||||
case v0alpha1.SchemeGroupVersion.WithResource("receivers"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Notifications().V0alpha1().Receivers().Informer()}, nil
|
||||
case v0alpha1.SchemeGroupVersion.WithResource("routingtrees"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Notifications().V0alpha1().RoutingTrees().Informer()}, nil
|
||||
case v0alpha1.SchemeGroupVersion.WithResource("templategroups"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Notifications().V0alpha1().TemplateGroups().Informer()}, nil
|
||||
case v0alpha1.SchemeGroupVersion.WithResource("timeintervals"):
|
||||
|
||||
@@ -12,6 +12,14 @@ type ReceiverListerExpansion interface{}
|
||||
// ReceiverNamespaceLister.
|
||||
type ReceiverNamespaceListerExpansion interface{}
|
||||
|
||||
// RoutingTreeListerExpansion allows custom methods to be added to
|
||||
// RoutingTreeLister.
|
||||
type RoutingTreeListerExpansion interface{}
|
||||
|
||||
// RoutingTreeNamespaceListerExpansion allows custom methods to be added to
|
||||
// RoutingTreeNamespaceLister.
|
||||
type RoutingTreeNamespaceListerExpansion interface{}
|
||||
|
||||
// TemplateGroupListerExpansion allows custom methods to be added to
|
||||
// TemplateGroupLister.
|
||||
type TemplateGroupListerExpansion interface{}
|
||||
|
||||
@@ -0,0 +1,56 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/client-go/listers"
|
||||
"k8s.io/client-go/tools/cache"
|
||||
)
|
||||
|
||||
// RoutingTreeLister helps list RoutingTrees.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type RoutingTreeLister interface {
|
||||
// List lists all RoutingTrees in the indexer.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []*v0alpha1.RoutingTree, err error)
|
||||
// RoutingTrees returns an object that can list and get RoutingTrees.
|
||||
RoutingTrees(namespace string) RoutingTreeNamespaceLister
|
||||
RoutingTreeListerExpansion
|
||||
}
|
||||
|
||||
// routingTreeLister implements the RoutingTreeLister interface.
|
||||
type routingTreeLister struct {
|
||||
listers.ResourceIndexer[*v0alpha1.RoutingTree]
|
||||
}
|
||||
|
||||
// NewRoutingTreeLister returns a new RoutingTreeLister.
|
||||
func NewRoutingTreeLister(indexer cache.Indexer) RoutingTreeLister {
|
||||
return &routingTreeLister{listers.New[*v0alpha1.RoutingTree](indexer, v0alpha1.Resource("routingtree"))}
|
||||
}
|
||||
|
||||
// RoutingTrees returns an object that can list and get RoutingTrees.
|
||||
func (s *routingTreeLister) RoutingTrees(namespace string) RoutingTreeNamespaceLister {
|
||||
return routingTreeNamespaceLister{listers.NewNamespaced[*v0alpha1.RoutingTree](s.ResourceIndexer, namespace)}
|
||||
}
|
||||
|
||||
// RoutingTreeNamespaceLister helps list and get RoutingTrees.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type RoutingTreeNamespaceLister interface {
|
||||
// List lists all RoutingTrees in the indexer for a given namespace.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []*v0alpha1.RoutingTree, err error)
|
||||
// Get retrieves the RoutingTree from the indexer for a given namespace and name.
|
||||
// Objects returned here must be treated as read-only.
|
||||
Get(name string) (*v0alpha1.RoutingTree, error)
|
||||
RoutingTreeNamespaceListerExpansion
|
||||
}
|
||||
|
||||
// routingTreeNamespaceLister implements the RoutingTreeNamespaceLister
|
||||
// interface.
|
||||
type routingTreeNamespaceLister struct {
|
||||
listers.ResourceIndexer[*v0alpha1.RoutingTree]
|
||||
}
|
||||
@@ -14,6 +14,7 @@ import (
|
||||
|
||||
notificationsModels "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
receiver "github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/receiver"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/routing_tree"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/template_group"
|
||||
timeInterval "github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/timeinterval"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
@@ -89,10 +90,16 @@ func (t *NotificationsAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiser
|
||||
return fmt.Errorf("failed to initialize templates group storage: %w", err)
|
||||
}
|
||||
|
||||
routeStorage, err := routing_tree.NewStorage(t.ng.Api.Policies, t.namespacer)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize route storage: %w", err)
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[notificationsModels.VERSION] = map[string]rest.Storage{
|
||||
notificationsModels.TimeIntervalResourceInfo.StoragePath(): intervals,
|
||||
notificationsModels.ReceiverResourceInfo.StoragePath(): recvStorage,
|
||||
notificationsModels.TemplateGroupResourceInfo.StoragePath(): templ,
|
||||
notificationsModels.RouteResourceInfo.StoragePath(): routeStorage,
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -117,6 +124,7 @@ func (t *NotificationsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3
|
||||
delete(oas.Paths.Paths, root+notificationsModels.ReceiverResourceInfo.GroupResource().Resource)
|
||||
delete(oas.Paths.Paths, root+notificationsModels.TimeIntervalResourceInfo.GroupResource().Resource)
|
||||
delete(oas.Paths.Paths, root+notificationsModels.TemplateGroupResourceInfo.GroupResource().Resource)
|
||||
delete(oas.Paths.Paths, root+notificationsModels.RouteResourceInfo.GroupResource().Resource)
|
||||
|
||||
// The root API discovery list
|
||||
sub := oas.Paths.Paths[root]
|
||||
@@ -136,6 +144,8 @@ func (t *NotificationsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||
return timeInterval.Authorize(ctx, t.authz, a)
|
||||
case notificationsModels.ReceiverResourceInfo.GroupResource().Resource:
|
||||
return receiver.Authorize(ctx, t.receiverAuth, a)
|
||||
case notificationsModels.RouteResourceInfo.GroupResource().Resource:
|
||||
return routing_tree.Authorize(ctx, t.authz, a)
|
||||
}
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
})
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
package routing_tree
|
||||
|
||||
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) {
|
||||
if attr.GetResource() != resourceInfo.GroupResource().Resource {
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
}
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return authorizer.DecisionDeny, "valid user is required", err
|
||||
}
|
||||
|
||||
var action accesscontrol.Evaluator
|
||||
switch attr.GetVerb() {
|
||||
case "patch":
|
||||
fallthrough
|
||||
case "create":
|
||||
fallthrough
|
||||
case "update":
|
||||
fallthrough
|
||||
case "deletecollection":
|
||||
fallthrough
|
||||
case "delete":
|
||||
action = accesscontrol.EvalAny(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsWrite),
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingRoutesWrite),
|
||||
)
|
||||
}
|
||||
|
||||
eval := accesscontrol.EvalAny(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsRead),
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingRoutesRead),
|
||||
)
|
||||
if action != nil {
|
||||
eval = accesscontrol.EvalAll(eval, action)
|
||||
}
|
||||
|
||||
ok, err := ac.Evaluate(ctx, user, eval)
|
||||
if ok {
|
||||
return authorizer.DecisionAllow, "", nil
|
||||
}
|
||||
return authorizer.DecisionDeny, "", err
|
||||
}
|
||||
@@ -0,0 +1,231 @@
|
||||
package routing_tree
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"maps"
|
||||
"slices"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
promModel "github.com/prometheus/common/model"
|
||||
|
||||
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
func convertToK8sResource(orgID int64, r definitions.Route, version string, namespacer request.NamespaceMapper) (*model.RoutingTree, error) {
|
||||
spec := model.RoutingTreeSpec{
|
||||
Defaults: model.RouteDefaults{
|
||||
GroupBy: r.GroupByStr,
|
||||
GroupWait: optionalPrometheusDurationToString(r.GroupWait),
|
||||
GroupInterval: optionalPrometheusDurationToString(r.GroupInterval),
|
||||
RepeatInterval: optionalPrometheusDurationToString(r.RepeatInterval),
|
||||
Receiver: r.Receiver,
|
||||
},
|
||||
}
|
||||
for _, route := range r.Routes {
|
||||
if route == nil {
|
||||
continue
|
||||
}
|
||||
spec.Routes = append(spec.Routes, convertRouteToK8sSubRoute(route))
|
||||
}
|
||||
|
||||
var result = &model.RoutingTree{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: model.RouteResourceInfo.GroupVersionKind().Kind,
|
||||
APIVersion: model.APIVERSION,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: model.UserDefinedRoutingTreeName,
|
||||
Namespace: namespacer(orgID),
|
||||
ResourceVersion: version,
|
||||
},
|
||||
Spec: spec,
|
||||
}
|
||||
result.SetProvenanceStatus(string(r.Provenance))
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func convertRouteToK8sSubRoute(r *definitions.Route) model.Route {
|
||||
result := model.Route{
|
||||
GroupBy: r.GroupByStr,
|
||||
MuteTimeIntervals: r.MuteTimeIntervals,
|
||||
Continue: r.Continue,
|
||||
GroupWait: optionalPrometheusDurationToString(r.GroupWait),
|
||||
GroupInterval: optionalPrometheusDurationToString(r.GroupInterval),
|
||||
RepeatInterval: optionalPrometheusDurationToString(r.RepeatInterval),
|
||||
Routes: make([]model.Route, 0, len(r.Routes)),
|
||||
}
|
||||
if r.Receiver != "" {
|
||||
result.Receiver = util.Pointer(r.Receiver)
|
||||
}
|
||||
|
||||
if r.Match != nil {
|
||||
keys := slices.Collect(maps.Keys(r.Match))
|
||||
slices.Sort(keys)
|
||||
for _, key := range keys {
|
||||
result.Matchers = append(result.Matchers, model.Matcher{
|
||||
Label: key,
|
||||
Type: model.MatcherTypeEqual,
|
||||
Value: r.Match[key],
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
if r.MatchRE != nil {
|
||||
keys := slices.Collect(maps.Keys(r.MatchRE))
|
||||
slices.Sort(keys)
|
||||
for _, key := range keys {
|
||||
m := model.Matcher{
|
||||
Label: key,
|
||||
Type: model.MatcherTypeEqualRegex,
|
||||
}
|
||||
value, _ := r.MatchRE[key].MarshalYAML()
|
||||
if s, ok := value.(string); ok {
|
||||
m.Value = s
|
||||
}
|
||||
result.Matchers = append(result.Matchers, m)
|
||||
}
|
||||
}
|
||||
|
||||
for _, m := range r.Matchers {
|
||||
result.Matchers = append(result.Matchers, model.Matcher{
|
||||
Label: m.Name,
|
||||
Type: model.MatcherType(m.Type.String()),
|
||||
Value: m.Value,
|
||||
})
|
||||
}
|
||||
for _, m := range r.ObjectMatchers {
|
||||
result.Matchers = append(result.Matchers, model.Matcher{
|
||||
Label: m.Name,
|
||||
Type: model.MatcherType(m.Type.String()),
|
||||
Value: m.Value,
|
||||
})
|
||||
}
|
||||
for _, route := range r.Routes {
|
||||
if route == nil {
|
||||
continue
|
||||
}
|
||||
result.Routes = append(result.Routes, convertRouteToK8sSubRoute(route))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func convertToDomainModel(obj *model.RoutingTree) (definitions.Route, string, error) {
|
||||
defaults := obj.Spec.Defaults
|
||||
result := definitions.Route{
|
||||
Receiver: defaults.Receiver,
|
||||
GroupByStr: defaults.GroupBy,
|
||||
Routes: make([]*definitions.Route, 0, len(obj.Spec.Routes)),
|
||||
}
|
||||
path := "."
|
||||
var errs []error
|
||||
|
||||
result.GroupWait = parsePrometheusDuration(defaults.GroupWait, func(err error) {
|
||||
errs = append(errs, fmt.Errorf("obj '%s' has invalid format of 'groupWait': %w", path, err))
|
||||
})
|
||||
result.GroupInterval = parsePrometheusDuration(defaults.GroupInterval, func(err error) {
|
||||
errs = append(errs, fmt.Errorf("obj '%s' has invalid format of 'groupInterval': %w", path, err))
|
||||
})
|
||||
result.RepeatInterval = parsePrometheusDuration(defaults.RepeatInterval, func(err error) {
|
||||
errs = append(errs, fmt.Errorf("obj '%s' has invalid format of 'repeatInterval': %w", path, err))
|
||||
})
|
||||
|
||||
for idx, route := range obj.Spec.Routes {
|
||||
p := fmt.Sprintf("%s[%d]", path, idx)
|
||||
s, err := convertK8sSubRouteToRoute(route, p)
|
||||
if len(err) > 0 {
|
||||
errs = append(errs, err...)
|
||||
} else {
|
||||
result.Routes = append(result.Routes, &s)
|
||||
}
|
||||
}
|
||||
if len(errs) > 0 {
|
||||
return definitions.Route{}, "", errors.Join(errs...)
|
||||
}
|
||||
result.Provenance = ""
|
||||
return result, obj.ResourceVersion, nil
|
||||
}
|
||||
|
||||
func convertK8sSubRouteToRoute(r model.Route, path string) (definitions.Route, []error) {
|
||||
result := definitions.Route{
|
||||
GroupByStr: r.GroupBy,
|
||||
MuteTimeIntervals: r.MuteTimeIntervals,
|
||||
Routes: make([]*definitions.Route, 0, len(r.Routes)),
|
||||
Matchers: make(config.Matchers, 0, len(r.Matchers)),
|
||||
Continue: r.Continue,
|
||||
}
|
||||
if r.Receiver != nil {
|
||||
result.Receiver = *r.Receiver
|
||||
}
|
||||
var errs []error
|
||||
result.GroupWait = parsePrometheusDuration(r.GroupWait, func(err error) {
|
||||
errs = append(errs, fmt.Errorf("route '%s' has invalid format of 'groupWait': %w", path, err))
|
||||
})
|
||||
result.GroupInterval = parsePrometheusDuration(r.GroupInterval, func(err error) {
|
||||
errs = append(errs, fmt.Errorf("route '%s' has invalid format of 'groupInterval': %w", path, err))
|
||||
})
|
||||
result.RepeatInterval = parsePrometheusDuration(r.RepeatInterval, func(err error) {
|
||||
errs = append(errs, fmt.Errorf("route '%s' has invalid format of 'repeatInterval': %w", path, err))
|
||||
})
|
||||
|
||||
for _, matcher := range r.Matchers {
|
||||
var mt labels.MatchType
|
||||
switch matcher.Type {
|
||||
case model.MatcherTypeEqual:
|
||||
mt = labels.MatchEqual
|
||||
case model.MatcherTypeNotEqual:
|
||||
mt = labels.MatchNotEqual
|
||||
case model.MatcherTypeEqualRegex:
|
||||
mt = labels.MatchRegexp
|
||||
case model.MatcherTypeNotEqualRegex:
|
||||
mt = labels.MatchNotRegexp
|
||||
default:
|
||||
errs = append(errs, fmt.Errorf("route '%s' has unsupported matcher type: %s", path, matcher.Type))
|
||||
continue
|
||||
}
|
||||
|
||||
m, err := labels.NewMatcher(mt, matcher.Label, matcher.Value)
|
||||
if err != nil {
|
||||
errs = append(errs, fmt.Errorf("route '%s' has illegal matcher: %w", path, err))
|
||||
continue
|
||||
}
|
||||
result.ObjectMatchers = append(result.ObjectMatchers, m)
|
||||
}
|
||||
|
||||
for idx, route := range r.Routes {
|
||||
p := fmt.Sprintf("%s[%d]", path, idx)
|
||||
s, err := convertK8sSubRouteToRoute(route, p)
|
||||
if len(err) > 0 {
|
||||
errs = append(errs, err...)
|
||||
} else {
|
||||
result.Routes = append(result.Routes, &s)
|
||||
}
|
||||
}
|
||||
return result, errs
|
||||
}
|
||||
|
||||
func optionalPrometheusDurationToString(d *promModel.Duration) *string {
|
||||
if d != nil {
|
||||
result := d.String()
|
||||
return &result
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func parsePrometheusDuration(s *string, callback func(e error)) *promModel.Duration {
|
||||
if s == nil || *s == "" {
|
||||
return nil
|
||||
}
|
||||
d, err := promModel.ParseDuration(*s)
|
||||
if err != nil {
|
||||
callback(err)
|
||||
return nil
|
||||
}
|
||||
return &d
|
||||
}
|
||||
@@ -0,0 +1,166 @@
|
||||
package routing_tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
notifications "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
grafanaRest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
alerting_models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
var (
|
||||
_ grafanaRest.LegacyStorage = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
var resourceInfo = notifications.RouteResourceInfo
|
||||
|
||||
type RouteService interface {
|
||||
GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, string, error)
|
||||
UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p alerting_models.Provenance, version string) (definitions.Route, string, error)
|
||||
ResetPolicyTree(ctx context.Context, orgID int64, p alerting_models.Provenance) (definitions.Route, error)
|
||||
}
|
||||
|
||||
type legacyStorage struct {
|
||||
service RouteService
|
||||
namespacer request.NamespaceMapper
|
||||
tableConverter rest.TableConvertor
|
||||
}
|
||||
|
||||
func (s *legacyStorage) New() runtime.Object {
|
||||
return resourceInfo.NewFunc()
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Destroy() {}
|
||||
|
||||
func (s *legacyStorage) NamespaceScoped() bool {
|
||||
return true // namespace == org
|
||||
}
|
||||
|
||||
func (s *legacyStorage) GetSingularName() string {
|
||||
return resourceInfo.GetSingularName()
|
||||
}
|
||||
|
||||
func (s *legacyStorage) NewList() runtime.Object {
|
||||
return resourceInfo.NewListFunc()
|
||||
}
|
||||
|
||||
func (s *legacyStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return s.tableConverter.ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) getUserDefinedRoutingTree(ctx context.Context) (*notifications.RoutingTree, error) {
|
||||
orgId, err := request.OrgIDForList(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
res, version, err := s.service.GetPolicyTree(ctx, orgId)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return convertToK8sResource(orgId, res, version, s.namespacer)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) List(ctx context.Context, _ *internalversion.ListOptions) (runtime.Object, error) {
|
||||
user, err := s.getUserDefinedRoutingTree(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ¬ifications.RoutingTreeList{
|
||||
Items: []notifications.RoutingTree{
|
||||
*user,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Get(ctx context.Context, name string, _ *metav1.GetOptions) (runtime.Object, error) {
|
||||
if name != notifications.UserDefinedRoutingTreeName {
|
||||
return nil, errors.NewNotFound(resourceInfo.GroupResource(), name)
|
||||
}
|
||||
return s.getUserDefinedRoutingTree(ctx)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Create(_ context.Context,
|
||||
_ runtime.Object,
|
||||
_ rest.ValidateObjectFunc,
|
||||
_ *metav1.CreateOptions,
|
||||
) (runtime.Object, error) {
|
||||
return nil, errors.NewMethodNotSupported(resourceInfo.GroupResource(), "create")
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Update(ctx context.Context, name string, objInfo rest.UpdatedObjectInfo, _ rest.ValidateObjectFunc, updateValidation rest.ValidateObjectUpdateFunc, _ bool, options *metav1.UpdateOptions) (runtime.Object, bool, error) {
|
||||
if name != notifications.UserDefinedRoutingTreeName {
|
||||
return nil, false, errors.NewNotFound(resourceInfo.GroupResource(), name)
|
||||
}
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
old, err := s.Get(ctx, notifications.UserDefinedRoutingTreeName, nil)
|
||||
if err != nil {
|
||||
return old, false, err
|
||||
}
|
||||
obj, err := objInfo.UpdatedObject(ctx, old)
|
||||
if err != nil {
|
||||
return old, false, err
|
||||
}
|
||||
if updateValidation != nil {
|
||||
if err := updateValidation(ctx, obj, old); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
p, ok := obj.(*notifications.RoutingTree)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("expected %s but got %s", notifications.ReceiverResourceInfo.GroupVersionKind(), obj.GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
|
||||
model, version, err := convertToDomainModel(p)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
updated, updatedVersion, err := s.service.UpdatePolicyTree(ctx, info.OrgID, model, alerting_models.ProvenanceNone, version)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
obj, err = convertToK8sResource(info.OrgID, updated, updatedVersion, s.namespacer)
|
||||
return obj, false, err
|
||||
}
|
||||
|
||||
// Delete implements rest.GracefulDeleter. It is needed for API server to not crash when it registers DeleteCollection method
|
||||
func (s *legacyStorage) Delete(ctx context.Context, name string, deleteValidation rest.ValidateObjectFunc, opts *metav1.DeleteOptions) (runtime.Object, bool, error) {
|
||||
if name != notifications.UserDefinedRoutingTreeName {
|
||||
return nil, false, errors.NewNotFound(resourceInfo.GroupResource(), name)
|
||||
}
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
|
||||
old, err := s.Get(ctx, name, nil)
|
||||
if err != nil {
|
||||
return old, false, err
|
||||
}
|
||||
|
||||
if deleteValidation != nil {
|
||||
if err = deleteValidation(ctx, old); err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
}
|
||||
_, err = s.service.ResetPolicyTree(ctx, info.OrgID, alerting_models.ProvenanceNone) // TODO add support for dry-run option
|
||||
return old, false, err
|
||||
}
|
||||
|
||||
func (s *legacyStorage) DeleteCollection(_ context.Context, _ rest.ValidateObjectFunc, _ *metav1.DeleteOptions, _ *internalversion.ListOptions) (runtime.Object, error) {
|
||||
return nil, errors.NewMethodNotSupported(resourceInfo.GroupResource(), "delete")
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
package routing_tree
|
||||
|
||||
import (
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
)
|
||||
|
||||
func NewStorage(legacySvc RouteService, namespacer request.NamespaceMapper) (rest.Storage, error) {
|
||||
legacyStore := &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
|
||||
}
|
||||
@@ -460,6 +460,10 @@ const (
|
||||
ActionAlertingReceiversPermissionsRead = "receivers.permissions:read"
|
||||
ActionAlertingReceiversPermissionsWrite = "receivers.permissions:write"
|
||||
|
||||
// Alerting routes policies actions
|
||||
ActionAlertingRoutesRead = "alert.notifications.routes:read"
|
||||
ActionAlertingRoutesWrite = "alert.notifications.routes:write"
|
||||
|
||||
// External alerting rule actions. We can only narrow it down to writes or reads, as we don't control the atomicity in the external system.
|
||||
ActionAlertingRuleExternalWrite = "alert.rules.external:write"
|
||||
ActionAlertingRuleExternalRead = "alert.rules.external:read"
|
||||
|
||||
@@ -258,6 +258,6 @@ func updateNotificationPolicyTree(t *testing.T, ctx context.Context, service *Se
|
||||
Routes: []*definition.Route{&child},
|
||||
}
|
||||
|
||||
err := service.ngAlert.Api.Policies.UpdatePolicyTree(ctx, user.GetOrgID(), tree, "", "")
|
||||
_, _, err := service.ngAlert.Api.Policies.UpdatePolicyTree(ctx, user.GetOrgID(), tree, "", "")
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
@@ -204,15 +204,39 @@ var (
|
||||
},
|
||||
}
|
||||
|
||||
routesReaderRole = accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: accesscontrol.FixedRolePrefix + "alerting.routes:reader",
|
||||
DisplayName: "Notification Policies Reader",
|
||||
Description: "Read all notification policies in Grafana alerting",
|
||||
Group: AlertRolesGroup,
|
||||
Permissions: accesscontrol.ConcatPermissions([]accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionAlertingRoutesRead},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
routesWriterRole = accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: accesscontrol.FixedRolePrefix + "alerting.routes:writer",
|
||||
DisplayName: "Notification Policies Writer",
|
||||
Description: "Update and reset notification policies in Grafana alerting",
|
||||
Group: AlertRolesGroup,
|
||||
Permissions: accesscontrol.ConcatPermissions(routesReaderRole.Role.Permissions, []accesscontrol.Permission{
|
||||
{Action: accesscontrol.ActionAlertingRoutesWrite},
|
||||
}),
|
||||
},
|
||||
}
|
||||
|
||||
notificationsReaderRole = accesscontrol.RoleRegistration{
|
||||
Role: accesscontrol.RoleDTO{
|
||||
Name: accesscontrol.FixedRolePrefix + "alerting.notifications:reader",
|
||||
DisplayName: "Notifications Reader",
|
||||
Description: "Read notification policies and contact points in Grafana and external providers",
|
||||
Group: AlertRolesGroup,
|
||||
Permissions: accesscontrol.ConcatPermissions(receiversReaderRole.Role.Permissions, templatesReaderRole.Role.Permissions, timeIntervalsReaderRole.Role.Permissions, []accesscontrol.Permission{
|
||||
Permissions: accesscontrol.ConcatPermissions(receiversReaderRole.Role.Permissions, templatesReaderRole.Role.Permissions, timeIntervalsReaderRole.Role.Permissions, routesReaderRole.Role.Permissions, []accesscontrol.Permission{
|
||||
{
|
||||
Action: accesscontrol.ActionAlertingNotificationsRead,
|
||||
Action: accesscontrol.ActionAlertingNotificationsRead, // TODO remove when we decide tò limit access to raw config API
|
||||
},
|
||||
{
|
||||
Action: accesscontrol.ActionAlertingNotificationsExternalRead,
|
||||
@@ -228,9 +252,9 @@ var (
|
||||
DisplayName: "Notifications Writer",
|
||||
Description: "Add, update, and delete contact points and notification policies in Grafana and external providers",
|
||||
Group: AlertRolesGroup,
|
||||
Permissions: accesscontrol.ConcatPermissions(notificationsReaderRole.Role.Permissions, receiversWriterRole.Role.Permissions, templatesWriterRole.Role.Permissions, timeIntervalsWriterRole.Role.Permissions, []accesscontrol.Permission{
|
||||
Permissions: accesscontrol.ConcatPermissions(notificationsReaderRole.Role.Permissions, receiversWriterRole.Role.Permissions, templatesWriterRole.Role.Permissions, timeIntervalsWriterRole.Role.Permissions, routesWriterRole.Role.Permissions, []accesscontrol.Permission{
|
||||
{
|
||||
Action: accesscontrol.ActionAlertingNotificationsWrite,
|
||||
Action: accesscontrol.ActionAlertingNotificationsWrite, // TODO remove when we decide tò limit access to raw config API
|
||||
},
|
||||
{
|
||||
Action: accesscontrol.ActionAlertingNotificationsExternalWrite,
|
||||
@@ -360,7 +384,7 @@ func DeclareFixedRoles(service accesscontrol.Service, features featuremgmt.Featu
|
||||
}
|
||||
|
||||
if features.IsEnabledGlobally(featuremgmt.FlagAlertingApiServer) {
|
||||
fixedRoles = append(fixedRoles, receiversReaderRole, receiversCreatorRole, receiversWriterRole, templatesReaderRole, templatesWriterRole, timeIntervalsReaderRole, timeIntervalsWriterRole)
|
||||
fixedRoles = append(fixedRoles, receiversReaderRole, receiversCreatorRole, receiversWriterRole, templatesReaderRole, templatesWriterRole, timeIntervalsReaderRole, timeIntervalsWriterRole, routesReaderRole, routesWriterRole)
|
||||
}
|
||||
|
||||
return service.DeclareFixedRoles(fixedRoles...)
|
||||
|
||||
@@ -58,7 +58,10 @@ func (srv AlertmanagerSrv) k8sApiServiceGuard(currentConfig apimodels.GettableUs
|
||||
|
||||
func checkRoutes(currentConfig apimodels.GettableUserConfig, newConfig apimodels.PostableUserConfig) error {
|
||||
reporter := cmputil.DiffReporter{}
|
||||
options := []cmp.Option{cmp.Reporter(&reporter), cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(labels.Matcher{})}
|
||||
options := []cmp.Option{cmp.Reporter(&reporter), cmpopts.EquateEmpty(), cmpopts.IgnoreUnexported(labels.Matcher{}), cmp.Transformer("", func(regexp amConfig.Regexp) any {
|
||||
r, _ := regexp.MarshalYAML()
|
||||
return r
|
||||
})}
|
||||
routesEqual := cmp.Equal(currentConfig.AlertmanagerConfig.Route, newConfig.AlertmanagerConfig.Route, options...)
|
||||
if !routesEqual && currentConfig.AlertmanagerConfig.Route.Provenance != apimodels.Provenance(ngmodels.ProvenanceNone) {
|
||||
return fmt.Errorf("policies were provisioned and cannot be changed through the UI")
|
||||
|
||||
@@ -52,7 +52,7 @@ type TemplateService interface {
|
||||
|
||||
type NotificationPolicyService interface {
|
||||
GetPolicyTree(ctx context.Context, orgID int64) (definitions.Route, string, error)
|
||||
UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p alerting_models.Provenance, version string) error
|
||||
UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p alerting_models.Provenance, version string) (definitions.Route, string, error)
|
||||
ResetPolicyTree(ctx context.Context, orgID int64, provenance alerting_models.Provenance) (definitions.Route, error)
|
||||
}
|
||||
|
||||
@@ -109,7 +109,7 @@ func (srv *ProvisioningSrv) RouteGetPolicyTreeExport(c *contextmodel.ReqContext)
|
||||
|
||||
func (srv *ProvisioningSrv) RoutePutPolicyTree(c *contextmodel.ReqContext, tree definitions.Route) response.Response {
|
||||
provenance := determineProvenance(c)
|
||||
err := srv.policies.UpdatePolicyTree(c.Req.Context(), c.SignedInUser.GetOrgID(), tree, alerting_models.Provenance(provenance), "")
|
||||
_, _, err := srv.policies.UpdatePolicyTree(c.Req.Context(), c.SignedInUser.GetOrgID(), tree, alerting_models.Provenance(provenance), "")
|
||||
if errors.Is(err, store.ErrNoAlertmanagerConfiguration) {
|
||||
return ErrResp(http.StatusNotFound, err, "")
|
||||
}
|
||||
|
||||
@@ -2035,13 +2035,13 @@ func (f *fakeNotificationPolicyService) GetPolicyTree(ctx context.Context, orgID
|
||||
return result, "", nil
|
||||
}
|
||||
|
||||
func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance, _ string) error {
|
||||
func (f *fakeNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance, version string) (definitions.Route, string, error) {
|
||||
if orgID != 1 {
|
||||
return store.ErrNoAlertmanagerConfiguration
|
||||
return definitions.Route{}, "", store.ErrNoAlertmanagerConfiguration
|
||||
}
|
||||
f.tree = tree
|
||||
f.prov = p
|
||||
return nil
|
||||
return tree, "some", nil
|
||||
}
|
||||
|
||||
func (f *fakeNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) {
|
||||
@@ -2055,8 +2055,8 @@ func (f *fakeFailingNotificationPolicyService) GetPolicyTree(ctx context.Context
|
||||
return definitions.Route{}, "", fmt.Errorf("something went wrong")
|
||||
}
|
||||
|
||||
func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance, _ string) error {
|
||||
return fmt.Errorf("something went wrong")
|
||||
func (f *fakeFailingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance, version string) (definitions.Route, string, error) {
|
||||
return definitions.Route{}, "", fmt.Errorf("something went wrong")
|
||||
}
|
||||
|
||||
func (f *fakeFailingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) {
|
||||
@@ -2069,8 +2069,8 @@ func (f *fakeRejectingNotificationPolicyService) GetPolicyTree(ctx context.Conte
|
||||
return definitions.Route{}, "", nil
|
||||
}
|
||||
|
||||
func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance, _ string) error {
|
||||
return fmt.Errorf("%w: invalid policy tree", provisioning.ErrValidation)
|
||||
func (f *fakeRejectingNotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance, version string) (definitions.Route, string, error) {
|
||||
return definitions.Route{}, "", fmt.Errorf("%w: invalid policy tree", provisioning.ErrValidation)
|
||||
}
|
||||
|
||||
func (f *fakeRejectingNotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) {
|
||||
|
||||
@@ -28,6 +28,11 @@ var (
|
||||
|
||||
ErrContactPointReferenced = errutil.Conflict("alerting.notifications.contact-points.referenced", errutil.WithPublicMessage("Contact point is currently referenced by a notification policy."))
|
||||
ErrContactPointUsedInRule = errutil.Conflict("alerting.notifications.contact-points.used-by-rule", errutil.WithPublicMessage("Contact point is currently used in the notification settings of one or many alert rules."))
|
||||
|
||||
ErrRouteInvalidFormat = errutil.BadRequest("alerting.notifications.routes.invalidFormat").MustTemplate(
|
||||
"Invalid format of the submitted route.",
|
||||
errutil.WithPublic("Invalid format of the submitted route: {{.Public.Error}}. Correct the payload and try again."),
|
||||
)
|
||||
)
|
||||
|
||||
// MakeErrTimeIntervalInvalid creates an error with the ErrTimeIntervalInvalid template
|
||||
@@ -90,3 +95,12 @@ func MakeErrTimeIntervalDependentResourcesProvenance(usedByRoutes bool, rules []
|
||||
Public: data,
|
||||
})
|
||||
}
|
||||
|
||||
func MakeErrRouteInvalidFormat(err error) error {
|
||||
return ErrRouteInvalidFormat.Build(errutil.TemplateData{
|
||||
Public: map[string]any{
|
||||
"Error": err.Error(),
|
||||
},
|
||||
Error: err,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -61,40 +61,40 @@ func (nps *NotificationPolicyService) GetPolicyTree(ctx context.Context, orgID i
|
||||
return result, version, nil
|
||||
}
|
||||
|
||||
func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance, version string) error {
|
||||
func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgID int64, tree definitions.Route, p models.Provenance, version string) (definitions.Route, string, error) {
|
||||
err := tree.Validate()
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrValidation, err.Error())
|
||||
return definitions.Route{}, "", MakeErrRouteInvalidFormat(err)
|
||||
}
|
||||
|
||||
revision, err := nps.configStore.Get(ctx, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
return definitions.Route{}, "", err
|
||||
}
|
||||
|
||||
err = nps.checkOptimisticConcurrency(*revision.Config.AlertmanagerConfig.Route, p, version, "update")
|
||||
if err != nil {
|
||||
return err
|
||||
return definitions.Route{}, "", err
|
||||
}
|
||||
|
||||
// check that provenance is not changed in an invalid way
|
||||
storedProvenance, err := nps.provenanceStore.GetProvenance(ctx, &tree, orgID)
|
||||
if err != nil {
|
||||
return err
|
||||
return definitions.Route{}, "", err
|
||||
}
|
||||
if err := nps.validator(storedProvenance, p); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
receivers, err := nps.receiversToMap(revision.Config.AlertmanagerConfig.Receivers)
|
||||
if err != nil {
|
||||
return err
|
||||
return definitions.Route{}, "", err
|
||||
}
|
||||
|
||||
receivers := map[string]struct{}{}
|
||||
receivers[""] = struct{}{} // Allow empty receiver (inheriting from parent)
|
||||
for _, receiver := range revision.GetReceivers(nil) {
|
||||
receivers[receiver.Name] = struct{}{}
|
||||
}
|
||||
|
||||
err = tree.ValidateReceivers(receivers)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrValidation, err.Error())
|
||||
return definitions.Route{}, "", MakeErrRouteInvalidFormat(err)
|
||||
}
|
||||
|
||||
timeIntervals := map[string]struct{}{}
|
||||
@@ -106,17 +106,21 @@ func (nps *NotificationPolicyService) UpdatePolicyTree(ctx context.Context, orgI
|
||||
}
|
||||
err = tree.ValidateMuteTimes(timeIntervals)
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: %s", ErrValidation, err.Error())
|
||||
return definitions.Route{}, "", MakeErrRouteInvalidFormat(err)
|
||||
}
|
||||
|
||||
revision.Config.AlertmanagerConfig.Config.Route = &tree
|
||||
|
||||
return nps.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
err = nps.xact.InTransaction(ctx, func(ctx context.Context) error {
|
||||
if err := nps.configStore.Save(ctx, revision, orgID); err != nil {
|
||||
return err
|
||||
}
|
||||
return nps.provenanceStore.SetProvenance(ctx, &tree, orgID, p)
|
||||
})
|
||||
if err != nil {
|
||||
return definitions.Route{}, "", err
|
||||
}
|
||||
return tree, calculateRouteFingerprint(tree), nil
|
||||
}
|
||||
|
||||
func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID int64, provenance models.Provenance) (definitions.Route, error) {
|
||||
@@ -159,14 +163,6 @@ func (nps *NotificationPolicyService) ResetPolicyTree(ctx context.Context, orgID
|
||||
return *route, nil
|
||||
}
|
||||
|
||||
func (nps *NotificationPolicyService) receiversToMap(records []*definitions.PostableApiReceiver) (map[string]struct{}, error) {
|
||||
receivers := map[string]struct{}{}
|
||||
for _, receiver := range records {
|
||||
receivers[receiver.Name] = struct{}{}
|
||||
}
|
||||
return receivers, nil
|
||||
}
|
||||
|
||||
func (nps *NotificationPolicyService) ensureDefaultReceiverExists(cfg *definitions.PostableUserConfig, defaultCfg *definitions.PostableUserConfig) error {
|
||||
defaultRcv := cfg.AlertmanagerConfig.Route.Receiver
|
||||
|
||||
|
||||
@@ -84,8 +84,8 @@ func TestUpdatePolicyTree(t *testing.T) {
|
||||
"not-existing",
|
||||
},
|
||||
}
|
||||
err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, ErrValidation)
|
||||
_, _, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, ErrRouteInvalidFormat)
|
||||
})
|
||||
|
||||
t.Run("ErrValidation if root route has no receiver", func(t *testing.T) {
|
||||
@@ -97,8 +97,8 @@ func TestUpdatePolicyTree(t *testing.T) {
|
||||
newRoute := definitions.Route{
|
||||
Receiver: "",
|
||||
}
|
||||
err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, ErrValidation)
|
||||
_, _, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, ErrRouteInvalidFormat)
|
||||
})
|
||||
|
||||
t.Run("ErrValidation if referenced receiver does not exist", func(t *testing.T) {
|
||||
@@ -113,8 +113,8 @@ func TestUpdatePolicyTree(t *testing.T) {
|
||||
newRoute := definitions.Route{
|
||||
Receiver: "unknown",
|
||||
}
|
||||
err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, ErrValidation)
|
||||
_, _, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, ErrRouteInvalidFormat)
|
||||
|
||||
t.Run("including sub-routes", func(t *testing.T) {
|
||||
newRoute := definitions.Route{
|
||||
@@ -123,8 +123,8 @@ func TestUpdatePolicyTree(t *testing.T) {
|
||||
{Receiver: "unknown"},
|
||||
},
|
||||
}
|
||||
err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, ErrValidation)
|
||||
_, _, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, ErrRouteInvalidFormat)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -137,7 +137,7 @@ func TestUpdatePolicyTree(t *testing.T) {
|
||||
newRoute := definitions.Route{
|
||||
Receiver: rev.Config.AlertmanagerConfig.Receivers[0].Name,
|
||||
}
|
||||
err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, "wrong-version")
|
||||
_, _, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, "wrong-version")
|
||||
require.ErrorIs(t, err, ErrVersionConflict)
|
||||
})
|
||||
|
||||
@@ -161,7 +161,7 @@ func TestUpdatePolicyTree(t *testing.T) {
|
||||
return expectedErr
|
||||
}
|
||||
|
||||
err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
_, _, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceNone, defaultVersion)
|
||||
require.ErrorIs(t, err, expectedErr)
|
||||
|
||||
assert.Len(t, prov.Calls, 1)
|
||||
@@ -180,8 +180,10 @@ func TestUpdatePolicyTree(t *testing.T) {
|
||||
expectedRev.ConcurrencyToken = rev.ConcurrencyToken
|
||||
expectedRev.Config.AlertmanagerConfig.Route = &route
|
||||
|
||||
err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceAPI, defaultVersion)
|
||||
result, version, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceAPI, defaultVersion)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newRoute, result)
|
||||
assert.Equal(t, calculateRouteFingerprint(newRoute), version)
|
||||
|
||||
assert.Len(t, store.Calls, 2)
|
||||
assert.Equal(t, "Save", store.Calls[1].Method)
|
||||
@@ -210,8 +212,10 @@ func TestUpdatePolicyTree(t *testing.T) {
|
||||
expectedRev.Config.AlertmanagerConfig.Route = &newRoute
|
||||
expectedRev.ConcurrencyToken = rev.ConcurrencyToken
|
||||
|
||||
err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceAPI, "")
|
||||
result, version, err := sut.UpdatePolicyTree(context.Background(), orgID, newRoute, models.ProvenanceAPI, "")
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, newRoute, result)
|
||||
assert.Equal(t, calculateRouteFingerprint(newRoute), version)
|
||||
|
||||
assert.Len(t, store.Calls, 2)
|
||||
assert.Equal(t, "Save", store.Calls[1].Method)
|
||||
|
||||
@@ -31,7 +31,7 @@ func (c *defaultNotificationPolicyProvisioner) Provision(ctx context.Context,
|
||||
files []*AlertingFile) error {
|
||||
for _, file := range files {
|
||||
for _, np := range file.Policies {
|
||||
err := c.notificationPolicyService.UpdatePolicyTree(ctx, np.OrgID,
|
||||
_, _, err := c.notificationPolicyService.UpdatePolicyTree(ctx, np.OrgID,
|
||||
np.Policy, models.ProvenanceFile, "")
|
||||
if err != nil {
|
||||
return fmt.Errorf("%s: %w", file.Filename, err)
|
||||
|
||||
@@ -0,0 +1,643 @@
|
||||
package routing_tree
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/alerting/definition"
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/prometheus/alertmanager/pkg/labels"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/bus"
|
||||
"github.com/grafana/grafana/pkg/generated/clientset/versioned"
|
||||
"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/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"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"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"
|
||||
)
|
||||
|
||||
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 TestIntegrationNotAllowedMethods(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().RoutingTrees("default")
|
||||
|
||||
route := &v0alpha1.RoutingTree{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v0alpha1.RoutingTreeSpec{},
|
||||
}
|
||||
_, err = client.Create(ctx, route, v1.CreateOptions{})
|
||||
assert.Error(t, err)
|
||||
require.Truef(t, errors.IsMethodNotSupported(err), "Expected MethodNotSupported but got %s", err)
|
||||
|
||||
err = client.DeleteCollection(ctx, v1.DeleteOptions{}, v1.ListOptions{})
|
||||
assert.Error(t, err)
|
||||
require.Truef(t, errors.IsMethodNotSupported(err), "Expected MethodNotSupported 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
|
||||
}
|
||||
|
||||
reader := helper.CreateUser("RoutesReader", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
{
|
||||
Actions: []string{
|
||||
accesscontrol.ActionAlertingRoutesRead,
|
||||
},
|
||||
},
|
||||
})
|
||||
writer := helper.CreateUser("RoutesWriter", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
{
|
||||
Actions: []string{
|
||||
accesscontrol.ActionAlertingRoutesRead,
|
||||
accesscontrol.ActionAlertingRoutesWrite,
|
||||
},
|
||||
},
|
||||
})
|
||||
none := helper.CreateUser("RoutesNone", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{})
|
||||
legacyReader := helper.CreateUser("LegacyRoutesReader", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
{
|
||||
Actions: []string{
|
||||
accesscontrol.ActionAlertingNotificationsRead,
|
||||
},
|
||||
},
|
||||
})
|
||||
legacyWriter := helper.CreateUser("LegacyRoutesWriter", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
{
|
||||
Actions: []string{
|
||||
accesscontrol.ActionAlertingNotificationsRead,
|
||||
accesscontrol.ActionAlertingNotificationsWrite,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
testCases := []testCase{
|
||||
{
|
||||
user: none,
|
||||
},
|
||||
{
|
||||
user: org1.Admin,
|
||||
canRead: true,
|
||||
canUpdate: true,
|
||||
},
|
||||
{
|
||||
user: org1.Editor,
|
||||
canRead: true,
|
||||
canUpdate: true,
|
||||
},
|
||||
{
|
||||
user: org1.Viewer,
|
||||
canRead: true,
|
||||
},
|
||||
{
|
||||
user: reader,
|
||||
canRead: true,
|
||||
},
|
||||
{
|
||||
user: writer,
|
||||
canRead: true,
|
||||
canUpdate: true,
|
||||
},
|
||||
{
|
||||
user: legacyReader,
|
||||
canRead: true,
|
||||
},
|
||||
{
|
||||
user: legacyWriter,
|
||||
canRead: true,
|
||||
canUpdate: true,
|
||||
},
|
||||
}
|
||||
|
||||
admin := org1.Admin
|
||||
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
|
||||
require.NoError(t, err)
|
||||
adminClient := adminK8sClient.NotificationsV0alpha1().RoutingTrees("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().RoutingTrees("default")
|
||||
|
||||
if tc.canRead {
|
||||
t.Run("should be able to list routing trees", func(t *testing.T) {
|
||||
list, err := client.List(ctx, v1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Items, 1)
|
||||
require.Equal(t, v0alpha1.UserDefinedRoutingTreeName, list.Items[0].Name)
|
||||
})
|
||||
|
||||
t.Run("should be able to read routing trees by resource identifier", func(t *testing.T) {
|
||||
_, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
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("should be forbidden to list routing trees", func(t *testing.T) {
|
||||
_, err := client.List(ctx, v1.ListOptions{})
|
||||
require.Error(t, err)
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
|
||||
t.Run("should be forbidden to read routing tree by name", func(t *testing.T) {
|
||||
_, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.Error(t, err)
|
||||
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.Error(t, err)
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
current, err := adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
expected := current.DeepCopy()
|
||||
expected.Spec.Routes = []v0alpha1.Route{
|
||||
{
|
||||
Matchers: []v0alpha1.Matcher{
|
||||
{
|
||||
Label: "test",
|
||||
Type: v0alpha1.MatcherTypeEqual,
|
||||
Value: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
d, err := json.Marshal(expected)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.canUpdate {
|
||||
t.Run("should be able to update routing tree", func(t *testing.T) {
|
||||
updated, err := client.Update(ctx, expected, 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 := expected.DeepCopy()
|
||||
up.Name = "notFound"
|
||||
_, err := client.Update(ctx, up, v1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
t.Run("should be forbidden to update routing tree", func(t *testing.T) {
|
||||
_, err := client.Update(ctx, expected, v1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
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 := expected.DeepCopy()
|
||||
up.Name = "notFound"
|
||||
_, err := client.Update(ctx, up, v1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
if tc.canUpdate {
|
||||
t.Run("should be able to reset routing tree", func(t *testing.T) {
|
||||
err := client.Delete(ctx, expected.Name, v1.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.Error(t, err)
|
||||
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
t.Run("should be forbidden to reset routing tree", func(t *testing.T) {
|
||||
err := client.Delete(ctx, expected.Name, v1.DeleteOptions{})
|
||||
require.Error(t, err)
|
||||
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.Error(t, err)
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
})
|
||||
require.NoError(t, adminClient.Delete(ctx, expected.Name, v1.DeleteOptions{}))
|
||||
}
|
||||
})
|
||||
|
||||
err = adminClient.Delete(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.DeleteOptions{})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
}
|
||||
|
||||
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().RoutingTrees("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)
|
||||
|
||||
current, err := adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "none", current.GetProvenanceStatus())
|
||||
|
||||
t.Run("should provide provenance status", func(t *testing.T) {
|
||||
require.NoError(t, db.SetProvenance(ctx, &definitions.Route{}, admin.Identity.GetOrgID(), "API"))
|
||||
|
||||
got, err := adminClient.Get(ctx, current.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) {
|
||||
updated := current.DeepCopy()
|
||||
updated.Spec.Routes = []v0alpha1.Route{
|
||||
{
|
||||
Matchers: []v0alpha1.Matcher{
|
||||
{
|
||||
Label: "test",
|
||||
Type: v0alpha1.MatcherTypeNotEqual,
|
||||
Value: "123",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
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, current.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().RoutingTrees("default")
|
||||
|
||||
current, err := adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotEmpty(t, current.ResourceVersion)
|
||||
|
||||
t.Run("should forbid if version does not match", func(t *testing.T) {
|
||||
updated := current.DeepCopy()
|
||||
updated.ResourceVersion = "test"
|
||||
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
||||
require.Error(t, err)
|
||||
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 := current.DeepCopy()
|
||||
updated.Spec.Defaults.GroupBy = append(updated.Spec.Defaults.GroupBy, "data")
|
||||
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, updated.Spec, actualUpdated.Spec)
|
||||
require.NotEqual(t, updated.ResourceVersion, actualUpdated.ResourceVersion)
|
||||
})
|
||||
t.Run("should update if version is empty", func(t *testing.T) {
|
||||
current, err = adminClient.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
updated := current.DeepCopy()
|
||||
updated.ResourceVersion = ""
|
||||
updated.Spec.Routes = append(updated.Spec.Routes, v0alpha1.Route{Continue: true})
|
||||
|
||||
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, updated.Spec, actualUpdated.Spec)
|
||||
require.NotEqual(t, current.ResourceVersion, actualUpdated.ResourceVersion)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationDataConsistency(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)
|
||||
client := adminK8sClient.NotificationsV0alpha1().RoutingTrees("default")
|
||||
|
||||
receiver := "grafana-default-email"
|
||||
timeInterval := "test-time-interval"
|
||||
createRoute := func(t *testing.T, route definitions.Route) {
|
||||
t.Helper()
|
||||
cfg, _, _ := legacyCli.GetAlertmanagerConfigWithStatus(t)
|
||||
var receivers []*definitions.PostableApiReceiver
|
||||
for _, apiReceiver := range cfg.AlertmanagerConfig.Receivers {
|
||||
var recv []*definitions.PostableGrafanaReceiver
|
||||
for _, r := range apiReceiver.GettableGrafanaReceivers.GrafanaManagedReceivers {
|
||||
recv = append(recv, &definitions.PostableGrafanaReceiver{
|
||||
UID: r.UID,
|
||||
Name: r.Name,
|
||||
Type: r.Type,
|
||||
DisableResolveMessage: r.DisableResolveMessage,
|
||||
Settings: r.Settings,
|
||||
})
|
||||
}
|
||||
receivers = append(receivers, &definitions.PostableApiReceiver{
|
||||
Receiver: config.Receiver{Name: apiReceiver.Name},
|
||||
PostableGrafanaReceivers: definitions.PostableGrafanaReceivers{GrafanaManagedReceivers: recv},
|
||||
})
|
||||
}
|
||||
_, err := legacyCli.PostConfiguration(t, definitions.PostableUserConfig{
|
||||
AlertmanagerConfig: definitions.PostableApiAlertingConfig{
|
||||
Config: definition.Config{
|
||||
Route: &route,
|
||||
TimeIntervals: []config.TimeInterval{
|
||||
{
|
||||
Name: timeInterval,
|
||||
},
|
||||
},
|
||||
},
|
||||
Receivers: receivers,
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
}
|
||||
|
||||
var regex config.Regexp
|
||||
require.NoError(t, json.Unmarshal([]byte(`".*"`), ®ex))
|
||||
|
||||
ensureMatcher := func(t *testing.T, mt labels.MatchType, lbl, val string) *labels.Matcher {
|
||||
m, err := labels.NewMatcher(mt, lbl, val)
|
||||
require.NoError(t, err)
|
||||
return m
|
||||
}
|
||||
|
||||
t.Run("all matchers are handled", func(t *testing.T) {
|
||||
t.Run("can read all legacy matchers", func(t *testing.T) {
|
||||
route := definitions.Route{
|
||||
Receiver: receiver,
|
||||
Routes: []*definitions.Route{
|
||||
{
|
||||
Match: map[string]string{
|
||||
"label_match": "test-123",
|
||||
},
|
||||
MatchRE: map[string]config.Regexp{
|
||||
"label_re": regex,
|
||||
},
|
||||
Matchers: config.Matchers{
|
||||
ensureMatcher(t, labels.MatchRegexp, "label_matchers", "test-321"),
|
||||
},
|
||||
ObjectMatchers: definitions.ObjectMatchers{
|
||||
ensureMatcher(t, labels.MatchNotRegexp, "object-label-matchers", "test-456"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
createRoute(t, route)
|
||||
tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, []v0alpha1.Matcher{
|
||||
{
|
||||
Label: "label_match",
|
||||
Type: v0alpha1.MatcherTypeEqual,
|
||||
Value: "test-123",
|
||||
},
|
||||
{
|
||||
Label: "label_re",
|
||||
Type: v0alpha1.MatcherTypeEqualRegex,
|
||||
Value: ".*",
|
||||
},
|
||||
{
|
||||
Label: "label_matchers",
|
||||
Type: v0alpha1.MatcherTypeEqualRegex,
|
||||
Value: "test-321",
|
||||
},
|
||||
{
|
||||
Label: "object-label-matchers",
|
||||
Type: v0alpha1.MatcherTypeNotEqualRegex,
|
||||
Value: "test-456",
|
||||
},
|
||||
}, tree.Spec.Routes[0].Matchers)
|
||||
})
|
||||
t.Run("should save into ObjectMatchers", func(t *testing.T) {
|
||||
route := definitions.Route{
|
||||
Receiver: receiver,
|
||||
Routes: []*definitions.Route{
|
||||
{
|
||||
Match: map[string]string{
|
||||
"oldmatch": "123",
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchRE: map[string]config.Regexp{
|
||||
"oldmatchre": regex,
|
||||
},
|
||||
},
|
||||
{
|
||||
Matchers: config.Matchers{
|
||||
ensureMatcher(t, labels.MatchNotEqual, "matchers", "v"),
|
||||
},
|
||||
},
|
||||
{
|
||||
ObjectMatchers: definitions.ObjectMatchers{
|
||||
ensureMatcher(t, labels.MatchEqual, "t2", "v2"),
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
createRoute(t, route)
|
||||
cfg, _, _ := legacyCli.GetAlertmanagerConfigWithStatus(t)
|
||||
expectedRoutes := cfg.AlertmanagerConfig.Route.Routes // autogenerated route is the first one
|
||||
expectedRoutes[1].Match = nil
|
||||
expectedRoutes[1].ObjectMatchers = definitions.ObjectMatchers{
|
||||
ensureMatcher(t, labels.MatchEqual, "oldmatch", "123"),
|
||||
}
|
||||
expectedRoutes[2].MatchRE = nil
|
||||
expectedRoutes[2].ObjectMatchers = definitions.ObjectMatchers{
|
||||
ensureMatcher(t, labels.MatchRegexp, "oldmatchre", ".*"),
|
||||
}
|
||||
expectedRoutes[3].Matchers = nil
|
||||
expectedRoutes[3].ObjectMatchers = definitions.ObjectMatchers{
|
||||
ensureMatcher(t, labels.MatchNotEqual, "matchers", "v"),
|
||||
}
|
||||
|
||||
tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
_, err = client.Update(ctx, tree, v1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
cfg, _, _ = legacyCli.GetAlertmanagerConfigWithStatus(t)
|
||||
routes := cfg.AlertmanagerConfig.Route.Routes
|
||||
require.EqualValues(t, expectedRoutes, routes)
|
||||
})
|
||||
})
|
||||
|
||||
route := definitions.Route{
|
||||
Receiver: receiver,
|
||||
GroupByStr: []string{"test-123", "test-456"},
|
||||
GroupWait: util.Pointer(model.Duration(30 * time.Second)),
|
||||
GroupInterval: util.Pointer(model.Duration(1 * time.Minute)),
|
||||
RepeatInterval: util.Pointer(model.Duration(24 * time.Hour)),
|
||||
Routes: []*definitions.Route{
|
||||
{
|
||||
ObjectMatchers: definitions.ObjectMatchers{
|
||||
ensureMatcher(t, labels.MatchNotEqual, "m", "1"),
|
||||
ensureMatcher(t, labels.MatchEqual, "n", "1"),
|
||||
ensureMatcher(t, labels.MatchRegexp, "o", "1"),
|
||||
ensureMatcher(t, labels.MatchNotRegexp, "p", "1"),
|
||||
},
|
||||
Receiver: receiver,
|
||||
GroupByStr: []string{"test-789"},
|
||||
GroupWait: util.Pointer(model.Duration(2 * time.Minute)),
|
||||
GroupInterval: util.Pointer(model.Duration(5 * time.Minute)),
|
||||
RepeatInterval: util.Pointer(model.Duration(30 * time.Hour)),
|
||||
MuteTimeIntervals: []string{timeInterval},
|
||||
Continue: true,
|
||||
},
|
||||
},
|
||||
}
|
||||
createRoute(t, route)
|
||||
|
||||
t.Run("correctly reads all fields", func(t *testing.T) {
|
||||
tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, v0alpha1.RouteDefaults{
|
||||
Receiver: receiver,
|
||||
GroupBy: []string{"test-123", "test-456"},
|
||||
GroupWait: util.Pointer("30s"),
|
||||
GroupInterval: util.Pointer("1m"),
|
||||
RepeatInterval: util.Pointer("1d"),
|
||||
}, tree.Spec.Defaults)
|
||||
assert.Len(t, tree.Spec.Routes, 1)
|
||||
assert.Equal(t, v0alpha1.Route{
|
||||
Continue: true,
|
||||
Receiver: util.Pointer(receiver),
|
||||
GroupBy: []string{"test-789"},
|
||||
GroupWait: util.Pointer("2m"),
|
||||
GroupInterval: util.Pointer("5m"),
|
||||
RepeatInterval: util.Pointer("1d6h"),
|
||||
MuteTimeIntervals: []string{timeInterval},
|
||||
Matchers: []v0alpha1.Matcher{
|
||||
{
|
||||
Label: "m",
|
||||
Type: v0alpha1.MatcherTypeNotEqual,
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Label: "n",
|
||||
Type: v0alpha1.MatcherTypeEqual,
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Label: "o",
|
||||
Type: v0alpha1.MatcherTypeEqualRegex,
|
||||
Value: "1",
|
||||
},
|
||||
{
|
||||
Label: "p",
|
||||
Type: v0alpha1.MatcherTypeNotEqualRegex,
|
||||
Value: "1",
|
||||
},
|
||||
},
|
||||
}, tree.Spec.Routes[0])
|
||||
})
|
||||
|
||||
t.Run("correctly save all fields", func(t *testing.T) {
|
||||
before, status, body := legacyCli.GetAlertmanagerConfigWithStatus(t)
|
||||
require.Equalf(t, http.StatusOK, status, body)
|
||||
tree, err := client.Get(ctx, v0alpha1.UserDefinedRoutingTreeName, v1.GetOptions{})
|
||||
tree.Spec.Defaults.GroupBy = []string{"test-123", "test-456", "test-789"}
|
||||
require.NoError(t, err)
|
||||
_, err = client.Update(ctx, tree, v1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
before.AlertmanagerConfig.Route.GroupByStr = []string{"test-123", "test-456", "test-789"}
|
||||
before.AlertmanagerConfig.Route.GroupBy = []model.LabelName{"test-123", "test-456", "test-789"}
|
||||
|
||||
after, status, body := legacyCli.GetAlertmanagerConfigWithStatus(t)
|
||||
require.Equalf(t, http.StatusOK, status, body)
|
||||
require.Equal(t, before, after)
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user