From e121e2609f63d66f935b090f1a24c32e8a45bede Mon Sep 17 00:00:00 2001 From: Matthew Jacobson Date: Wed, 26 Jun 2024 13:03:25 -0400 Subject: [PATCH] Alerting: Receiver API (#89707) --- apps/alerting/notifications/receiver.cue | 32 +++ .../v0alpha1/receiver_spec.go | 21 ++ .../v0alpha1/register.go | 7 + .../alerting_notifications/v0alpha1/types.go | 81 +++++++ .../v0alpha1/zz_generated.deepcopy.go | 121 ++++++++++ .../v0alpha1/zz_generated.openapi.go | 198 +++++++++++++++ ...enerated.openapi_violation_exceptions.list | 1 + .../v0alpha1/integration.go | 69 ++++++ .../v0alpha1/receiver.go | 196 +++++++++++++++ .../v0alpha1/receiverspec.go | 39 +++ pkg/generated/applyconfiguration/utils.go | 6 + .../v0alpha1/alerting_notifications_client.go | 5 + .../fake_alerting_notifications_client.go | 4 + .../v0alpha1/fake/fake_receiver.go | 140 +++++++++++ .../v0alpha1/generated_expansion.go | 2 + .../v0alpha1/receiver.go | 194 +++++++++++++++ .../v0alpha1/interface.go | 7 + .../v0alpha1/receiver.go | 76 ++++++ .../informers/externalversions/generic.go | 2 + .../v0alpha1/expansion_generated.go | 8 + .../v0alpha1/receiver.go | 85 +++++++ .../notifications/receiver/authorize.go | 53 +++++ .../notifications/receiver/conversions.go | 101 ++++++++ .../notifications/receiver/legacy_storage.go | 225 ++++++++++++++++++ .../notifications/receiver/storage.go | 80 +++++++ .../apis/alerting/notifications/register.go | 9 + 26 files changed, 1762 insertions(+) create mode 100644 apps/alerting/notifications/receiver.cue create mode 100644 pkg/apis/alerting_notifications/v0alpha1/receiver_spec.go create mode 100644 pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/integration.go create mode 100644 pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/receiver.go create mode 100644 pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/receiverspec.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_receiver.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/receiver.go create mode 100644 pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/receiver.go create mode 100644 pkg/generated/listers/alerting_notifications/v0alpha1/receiver.go create mode 100644 pkg/registry/apis/alerting/notifications/receiver/authorize.go create mode 100644 pkg/registry/apis/alerting/notifications/receiver/conversions.go create mode 100644 pkg/registry/apis/alerting/notifications/receiver/legacy_storage.go create mode 100644 pkg/registry/apis/alerting/notifications/receiver/storage.go diff --git a/apps/alerting/notifications/receiver.cue b/apps/alerting/notifications/receiver.cue new file mode 100644 index 00000000000..3fe31a26684 --- /dev/null +++ b/apps/alerting/notifications/receiver.cue @@ -0,0 +1,32 @@ +package core + +receiver: { + kind: "Receiver" + group: "notifications" + apiResource: { + groupOverride: "notifications.alerting.grafana.app" + } + codegen: { + frontend: false + backend: true + } + pluralName: "Receivers" + current: "v0alpha1" + versions: { + "v0alpha1": { + schema: { + #Integration: { + uid?: string + type: string + disableResolveMessage?: bool + settings: bytes + secureFields?: [string]: bool + } + spec: { + title: string + integrations : [...#Integration] + } + } + } + } +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/receiver_spec.go b/pkg/apis/alerting_notifications/v0alpha1/receiver_spec.go new file mode 100644 index 00000000000..9adcef30e3f --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/receiver_spec.go @@ -0,0 +1,21 @@ +package v0alpha1 + +// Integration defines model for Integration. +// +k8s:openapi-gen=true +type Integration struct { + DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"` + // +mapType=atomic + SecureFields map[string]bool `json:"SecureFields,omitempty"` + // +listType=atomic + Settings []byte `json:"settings"` + Type string `json:"type"` + Uid *string `json:"uid,omitempty"` +} + +// ReceiverSpec defines model for Spec. +// +k8s:openapi-gen=true +type ReceiverSpec struct { + // +listType=atomic + Integrations []Integration `json:"integrations"` + Title string `json:"title"` +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/register.go b/pkg/apis/alerting_notifications/v0alpha1/register.go index a6be83e04ef..cbf36e4aab4 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/register.go +++ b/pkg/apis/alerting_notifications/v0alpha1/register.go @@ -24,6 +24,11 @@ var ( func() runtime.Object { return &TimeInterval{} }, func() runtime.Object { return &TimeIntervalList{} }, ) + ReceiverResourceInfo = common.NewResourceInfo(GROUP, VERSION, + "receivers", "receiver", "Receiver", + func() runtime.Object { return &Receiver{} }, + func() runtime.Object { return &ReceiverList{} }, + ) // SchemeGroupVersion is group version used to register these objects SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION} // SchemaBuilder is used by standard codegen @@ -42,6 +47,8 @@ func AddKnownTypesGroup(scheme *runtime.Scheme, g schema.GroupVersion) error { scheme.AddKnownTypes(g, &TimeInterval{}, &TimeIntervalList{}, + &Receiver{}, + &ReceiverList{}, ) metav1.AddToGroupVersion(scheme, g) return nil diff --git a/pkg/apis/alerting_notifications/v0alpha1/types.go b/pkg/apis/alerting_notifications/v0alpha1/types.go index 6a970ad4516..ec5edaf6e6e 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/types.go +++ b/pkg/apis/alerting_notifications/v0alpha1/types.go @@ -85,3 +85,84 @@ type TimeIntervalList struct { metav1.ListMeta `json:"metadata"` Items []TimeInterval `json:"items"` } + +// Receivers --------------------------------- + +// +genclient +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type Receiver struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata"` + Spec ReceiverSpec `json:"spec"` +} + +func (o *Receiver) GetSpec() any { + return o.Spec +} + +func (o *Receiver) SetSpec(spec any) error { + cast, ok := spec.(ReceiverSpec) + if !ok { + return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec) + } + o.Spec = cast + return nil +} + +func (o *Receiver) GetCreatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/createdBy"] +} + +func (o *Receiver) SetCreatedBy(createdBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy +} + +func (o *Receiver) 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 *Receiver) 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 *Receiver) GetUpdatedBy() string { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + return o.ObjectMeta.Annotations["grafana.com/updatedBy"] +} + +func (o *Receiver) SetUpdatedBy(updatedBy string) { + if o.ObjectMeta.Annotations == nil { + o.ObjectMeta.Annotations = make(map[string]string) + } + + o.ObjectMeta.Annotations["grafana.com/updatedBy"] = updatedBy +} + +// +k8s:openapi-gen=true +// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object +type ReceiverList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + Items []Receiver `json:"items"` +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go index 918c3d81cb2..69f57b975f8 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go @@ -11,6 +11,44 @@ import ( runtime "k8s.io/apimachinery/pkg/runtime" ) +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *Integration) DeepCopyInto(out *Integration) { + *out = *in + if in.DisableResolveMessage != nil { + in, out := &in.DisableResolveMessage, &out.DisableResolveMessage + *out = new(bool) + **out = **in + } + if in.SecureFields != nil { + in, out := &in.SecureFields, &out.SecureFields + *out = make(map[string]bool, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } + if in.Settings != nil { + in, out := &in.Settings, &out.Settings + *out = make([]byte, len(*in)) + copy(*out, *in) + } + if in.Uid != nil { + in, out := &in.Uid, &out.Uid + *out = new(string) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Integration. +func (in *Integration) DeepCopy() *Integration { + if in == nil { + return nil + } + out := new(Integration) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Interval) DeepCopyInto(out *Interval) { *out = *in @@ -57,6 +95,89 @@ 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 *Receiver) DeepCopyInto(out *Receiver) { + *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 Receiver. +func (in *Receiver) DeepCopy() *Receiver { + if in == nil { + return nil + } + out := new(Receiver) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *Receiver) 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 *ReceiverList) DeepCopyInto(out *ReceiverList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]Receiver, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiverList. +func (in *ReceiverList) DeepCopy() *ReceiverList { + if in == nil { + return nil + } + out := new(ReceiverList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *ReceiverList) 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 *ReceiverSpec) DeepCopyInto(out *ReceiverSpec) { + *out = *in + if in.Integrations != nil { + in, out := &in.Integrations, &out.Integrations + *out = make([]Integration, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ReceiverSpec. +func (in *ReceiverSpec) DeepCopy() *ReceiverSpec { + if in == nil { + return nil + } + out := new(ReceiverSpec) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *TimeInterval) DeepCopyInto(out *TimeInterval) { *out = *in diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go index 8d5ff04f0a0..b0125bb8ca1 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go @@ -16,7 +16,11 @@ import ( func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition { 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.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.TimeInterval": schema_pkg_apis_alerting_notifications_v0alpha1_TimeInterval(ref), "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TimeIntervalList": schema_pkg_apis_alerting_notifications_v0alpha1_TimeIntervalList(ref), "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TimeIntervalSpec": schema_pkg_apis_alerting_notifications_v0alpha1_TimeIntervalSpec(ref), @@ -24,6 +28,70 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA } } +func schema_pkg_apis_alerting_notifications_v0alpha1_Integration(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "Integration defines model for Integration.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "disableResolveMessage": { + SchemaProps: spec.SchemaProps{ + Type: []string{"boolean"}, + Format: "", + }, + }, + "SecureFields": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-map-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"object"}, + AdditionalProperties: &spec.SchemaOrBool{ + Allows: true, + Schema: &spec.Schema{ + SchemaProps: spec.SchemaProps{ + Default: false, + Type: []string{"boolean"}, + Format: "", + }, + }, + }, + }, + }, + "settings": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "byte", + }, + }, + "type": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + "uid": { + SchemaProps: spec.SchemaProps{ + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"settings", "type"}, + }, + }, + } +} + func schema_pkg_apis_alerting_notifications_v0alpha1_Interval(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ @@ -144,6 +212,136 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_Interval(ref common.Referen } } +func schema_pkg_apis_alerting_notifications_v0alpha1_Receiver(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.ReceiverSpec"), + }, + }, + }, + Required: []string{"metadata", "spec"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.ReceiverSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"}, + } +} + +func schema_pkg_apis_alerting_notifications_v0alpha1_ReceiverList(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.Receiver"), + }, + }, + }, + }, + }, + }, + Required: []string{"metadata", "items"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Receiver", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"}, + } +} + +func schema_pkg_apis_alerting_notifications_v0alpha1_ReceiverSpec(ref common.ReferenceCallback) common.OpenAPIDefinition { + return common.OpenAPIDefinition{ + Schema: spec.Schema{ + SchemaProps: spec.SchemaProps{ + Description: "ReceiverSpec defines model for Spec.", + Type: []string{"object"}, + Properties: map[string]spec.Schema{ + "integrations": { + VendorExtensible: spec.VendorExtensible{ + Extensions: spec.Extensions{ + "x-kubernetes-list-type": "atomic", + }, + }, + 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.Integration"), + }, + }, + }, + }, + }, + "title": { + SchemaProps: spec.SchemaProps{ + Default: "", + Type: []string{"string"}, + Format: "", + }, + }, + }, + Required: []string{"integrations", "title"}, + }, + }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Integration"}, + } +} + func schema_pkg_apis_alerting_notifications_v0alpha1_TimeInterval(ref common.ReferenceCallback) common.OpenAPIDefinition { return common.OpenAPIDefinition{ Schema: spec.Schema{ diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi_violation_exceptions.list index 7b756e64dfd..ac3e0fa0bf0 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi_violation_exceptions.list +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -1,3 +1,4 @@ +API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,Integration,SecureFields 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,TimeIntervalSpec,TimeIntervals API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,TimeRange,EndTime diff --git a/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/integration.go b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/integration.go new file mode 100644 index 00000000000..b1bfb5e1be5 --- /dev/null +++ b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/integration.go @@ -0,0 +1,69 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// IntegrationApplyConfiguration represents an declarative configuration of the Integration type for use +// with apply. +type IntegrationApplyConfiguration struct { + DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"` + SecureFields map[string]bool `json:"SecureFields,omitempty"` + Settings []byte `json:"settings,omitempty"` + Type *string `json:"type,omitempty"` + Uid *string `json:"uid,omitempty"` +} + +// IntegrationApplyConfiguration constructs an declarative configuration of the Integration type for use with +// apply. +func Integration() *IntegrationApplyConfiguration { + return &IntegrationApplyConfiguration{} +} + +// WithDisableResolveMessage sets the DisableResolveMessage 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 DisableResolveMessage field is set to the value of the last call. +func (b *IntegrationApplyConfiguration) WithDisableResolveMessage(value bool) *IntegrationApplyConfiguration { + b.DisableResolveMessage = &value + return b +} + +// WithSecureFields puts the entries into the SecureFields 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 SecureFields field, +// overwriting an existing map entries in SecureFields field with the same key. +func (b *IntegrationApplyConfiguration) WithSecureFields(entries map[string]bool) *IntegrationApplyConfiguration { + if b.SecureFields == nil && len(entries) > 0 { + b.SecureFields = make(map[string]bool, len(entries)) + } + for k, v := range entries { + b.SecureFields[k] = v + } + return b +} + +// WithSettings adds the given value to the Settings 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 Settings field. +func (b *IntegrationApplyConfiguration) WithSettings(values ...byte) *IntegrationApplyConfiguration { + for i := range values { + b.Settings = append(b.Settings, values[i]) + } + 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 *IntegrationApplyConfiguration) WithType(value string) *IntegrationApplyConfiguration { + b.Type = &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 *IntegrationApplyConfiguration) WithUid(value string) *IntegrationApplyConfiguration { + b.Uid = &value + return b +} diff --git a/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/receiver.go b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/receiver.go new file mode 100644 index 00000000000..94565883187 --- /dev/null +++ b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/receiver.go @@ -0,0 +1,196 @@ +// 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" +) + +// ReceiverApplyConfiguration represents an declarative configuration of the Receiver type for use +// with apply. +type ReceiverApplyConfiguration struct { + v1.TypeMetaApplyConfiguration `json:",inline"` + *v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"` + Spec *ReceiverSpecApplyConfiguration `json:"spec,omitempty"` +} + +// Receiver constructs an declarative configuration of the Receiver type for use with +// apply. +func Receiver(name, namespace string) *ReceiverApplyConfiguration { + b := &ReceiverApplyConfiguration{} + b.WithName(name) + b.WithNamespace(namespace) + b.WithKind("Receiver") + 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 *ReceiverApplyConfiguration) WithKind(value string) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithAPIVersion(value string) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithName(value string) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithGenerateName(value string) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithNamespace(value string) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithUID(value types.UID) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithResourceVersion(value string) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithGeneration(value int64) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithCreationTimestamp(value metav1.Time) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithLabels(entries map[string]string) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithAnnotations(entries map[string]string) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *ReceiverApplyConfiguration { + 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 *ReceiverApplyConfiguration) WithFinalizers(values ...string) *ReceiverApplyConfiguration { + b.ensureObjectMetaApplyConfigurationExists() + for i := range values { + b.Finalizers = append(b.Finalizers, values[i]) + } + return b +} + +func (b *ReceiverApplyConfiguration) 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 *ReceiverApplyConfiguration) WithSpec(value *ReceiverSpecApplyConfiguration) *ReceiverApplyConfiguration { + b.Spec = value + return b +} diff --git a/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/receiverspec.go b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/receiverspec.go new file mode 100644 index 00000000000..d7fc1ba3039 --- /dev/null +++ b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/receiverspec.go @@ -0,0 +1,39 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +// ReceiverSpecApplyConfiguration represents an declarative configuration of the ReceiverSpec type for use +// with apply. +type ReceiverSpecApplyConfiguration struct { + Integrations []IntegrationApplyConfiguration `json:"integrations,omitempty"` + Title *string `json:"title,omitempty"` +} + +// ReceiverSpecApplyConfiguration constructs an declarative configuration of the ReceiverSpec type for use with +// apply. +func ReceiverSpec() *ReceiverSpecApplyConfiguration { + return &ReceiverSpecApplyConfiguration{} +} + +// WithIntegrations adds the given value to the Integrations 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 Integrations field. +func (b *ReceiverSpecApplyConfiguration) WithIntegrations(values ...*IntegrationApplyConfiguration) *ReceiverSpecApplyConfiguration { + for i := range values { + if values[i] == nil { + panic("nil value passed to WithIntegrations") + } + b.Integrations = append(b.Integrations, *values[i]) + } + return b +} + +// WithTitle sets the Title 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 Title field is set to the value of the last call. +func (b *ReceiverSpecApplyConfiguration) WithTitle(value string) *ReceiverSpecApplyConfiguration { + b.Title = &value + return b +} diff --git a/pkg/generated/applyconfiguration/utils.go b/pkg/generated/applyconfiguration/utils.go index 561bf3385e5..19f42b751e2 100644 --- a/pkg/generated/applyconfiguration/utils.go +++ b/pkg/generated/applyconfiguration/utils.go @@ -17,8 +17,14 @@ import ( func ForKind(kind schema.GroupVersionKind) interface{} { switch kind { // Group=notifications.alerting.grafana.app, Version=v0alpha1 + case v0alpha1.SchemeGroupVersion.WithKind("Integration"): + return &alertingnotificationsv0alpha1.IntegrationApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("Interval"): return &alertingnotificationsv0alpha1.IntervalApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("Receiver"): + return &alertingnotificationsv0alpha1.ReceiverApplyConfiguration{} + case v0alpha1.SchemeGroupVersion.WithKind("ReceiverSpec"): + return &alertingnotificationsv0alpha1.ReceiverSpecApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("TimeInterval"): return &alertingnotificationsv0alpha1.TimeIntervalApplyConfiguration{} case v0alpha1.SchemeGroupVersion.WithKind("TimeIntervalSpec"): diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/alerting_notifications_client.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/alerting_notifications_client.go index 5c81a66d6ec..53dfb5ad0d8 100644 --- a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/alerting_notifications_client.go +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/alerting_notifications_client.go @@ -14,6 +14,7 @@ import ( type NotificationsV0alpha1Interface interface { RESTClient() rest.Interface + ReceiversGetter TimeIntervalsGetter } @@ -22,6 +23,10 @@ type NotificationsV0alpha1Client struct { restClient rest.Interface } +func (c *NotificationsV0alpha1Client) Receivers(namespace string) ReceiverInterface { + return newReceivers(c, namespace) +} + func (c *NotificationsV0alpha1Client) TimeIntervals(namespace string) TimeIntervalInterface { return newTimeIntervals(c, namespace) } diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_alerting_notifications_client.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_alerting_notifications_client.go index ece678250d8..84bcadcd8e2 100644 --- a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_alerting_notifications_client.go +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_alerting_notifications_client.go @@ -14,6 +14,10 @@ type FakeNotificationsV0alpha1 struct { *testing.Fake } +func (c *FakeNotificationsV0alpha1) Receivers(namespace string) v0alpha1.ReceiverInterface { + return &FakeReceivers{c, namespace} +} + func (c *FakeNotificationsV0alpha1) TimeIntervals(namespace string) v0alpha1.TimeIntervalInterface { return &FakeTimeIntervals{c, namespace} } diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_receiver.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_receiver.go new file mode 100644 index 00000000000..bf508c34049 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_receiver.go @@ -0,0 +1,140 @@ +// 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" +) + +// FakeReceivers implements ReceiverInterface +type FakeReceivers struct { + Fake *FakeNotificationsV0alpha1 + ns string +} + +var receiversResource = v0alpha1.SchemeGroupVersion.WithResource("receivers") + +var receiversKind = v0alpha1.SchemeGroupVersion.WithKind("Receiver") + +// Get takes name of the receiver, and returns the corresponding receiver object, and an error if there is any. +func (c *FakeReceivers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v0alpha1.Receiver, err error) { + obj, err := c.Fake. + Invokes(testing.NewGetAction(receiversResource, c.ns, name), &v0alpha1.Receiver{}) + + if obj == nil { + return nil, err + } + return obj.(*v0alpha1.Receiver), err +} + +// List takes label and field selectors, and returns the list of Receivers that match those selectors. +func (c *FakeReceivers) List(ctx context.Context, opts v1.ListOptions) (result *v0alpha1.ReceiverList, err error) { + obj, err := c.Fake. + Invokes(testing.NewListAction(receiversResource, receiversKind, c.ns, opts), &v0alpha1.ReceiverList{}) + + if obj == nil { + return nil, err + } + + label, _, _ := testing.ExtractFromListOptions(opts) + if label == nil { + label = labels.Everything() + } + list := &v0alpha1.ReceiverList{ListMeta: obj.(*v0alpha1.ReceiverList).ListMeta} + for _, item := range obj.(*v0alpha1.ReceiverList).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 receivers. +func (c *FakeReceivers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + return c.Fake. + InvokesWatch(testing.NewWatchAction(receiversResource, c.ns, opts)) + +} + +// Create takes the representation of a receiver and creates it. Returns the server's representation of the receiver, and an error, if there is any. +func (c *FakeReceivers) Create(ctx context.Context, receiver *v0alpha1.Receiver, opts v1.CreateOptions) (result *v0alpha1.Receiver, err error) { + obj, err := c.Fake. + Invokes(testing.NewCreateAction(receiversResource, c.ns, receiver), &v0alpha1.Receiver{}) + + if obj == nil { + return nil, err + } + return obj.(*v0alpha1.Receiver), err +} + +// Update takes the representation of a receiver and updates it. Returns the server's representation of the receiver, and an error, if there is any. +func (c *FakeReceivers) Update(ctx context.Context, receiver *v0alpha1.Receiver, opts v1.UpdateOptions) (result *v0alpha1.Receiver, err error) { + obj, err := c.Fake. + Invokes(testing.NewUpdateAction(receiversResource, c.ns, receiver), &v0alpha1.Receiver{}) + + if obj == nil { + return nil, err + } + return obj.(*v0alpha1.Receiver), err +} + +// Delete takes name of the receiver and deletes it. Returns an error if one occurs. +func (c *FakeReceivers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + _, err := c.Fake. + Invokes(testing.NewDeleteActionWithOptions(receiversResource, c.ns, name, opts), &v0alpha1.Receiver{}) + + return err +} + +// DeleteCollection deletes a collection of objects. +func (c *FakeReceivers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + action := testing.NewDeleteCollectionAction(receiversResource, c.ns, listOpts) + + _, err := c.Fake.Invokes(action, &v0alpha1.ReceiverList{}) + return err +} + +// Patch applies the patch and returns the patched receiver. +func (c *FakeReceivers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v0alpha1.Receiver, err error) { + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(receiversResource, c.ns, name, pt, data, subresources...), &v0alpha1.Receiver{}) + + if obj == nil { + return nil, err + } + return obj.(*v0alpha1.Receiver), err +} + +// Apply takes the given apply declarative configuration, applies it and returns the applied receiver. +func (c *FakeReceivers) Apply(ctx context.Context, receiver *alertingnotificationsv0alpha1.ReceiverApplyConfiguration, opts v1.ApplyOptions) (result *v0alpha1.Receiver, err error) { + if receiver == nil { + return nil, fmt.Errorf("receiver provided to Apply must not be nil") + } + data, err := json.Marshal(receiver) + if err != nil { + return nil, err + } + name := receiver.Name + if name == nil { + return nil, fmt.Errorf("receiver.Name must be provided to Apply") + } + obj, err := c.Fake. + Invokes(testing.NewPatchSubresourceAction(receiversResource, c.ns, *name, types.ApplyPatchType, data), &v0alpha1.Receiver{}) + + if obj == nil { + return nil, err + } + return obj.(*v0alpha1.Receiver), err +} diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/generated_expansion.go index cf6e218f4ae..c30d1403330 100644 --- a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/generated_expansion.go +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/generated_expansion.go @@ -4,4 +4,6 @@ package v0alpha1 +type ReceiverExpansion interface{} + type TimeIntervalExpansion interface{} diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/receiver.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/receiver.go new file mode 100644 index 00000000000..1f99044e245 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/receiver.go @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by client-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + "context" + json "encoding/json" + "fmt" + "time" + + 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" + rest "k8s.io/client-go/rest" +) + +// ReceiversGetter has a method to return a ReceiverInterface. +// A group's client should implement this interface. +type ReceiversGetter interface { + Receivers(namespace string) ReceiverInterface +} + +// ReceiverInterface has methods to work with Receiver resources. +type ReceiverInterface interface { + Create(ctx context.Context, receiver *v0alpha1.Receiver, opts v1.CreateOptions) (*v0alpha1.Receiver, error) + Update(ctx context.Context, receiver *v0alpha1.Receiver, opts v1.UpdateOptions) (*v0alpha1.Receiver, 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.Receiver, error) + List(ctx context.Context, opts v1.ListOptions) (*v0alpha1.ReceiverList, 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.Receiver, err error) + Apply(ctx context.Context, receiver *alertingnotificationsv0alpha1.ReceiverApplyConfiguration, opts v1.ApplyOptions) (result *v0alpha1.Receiver, err error) + ReceiverExpansion +} + +// receivers implements ReceiverInterface +type receivers struct { + client rest.Interface + ns string +} + +// newReceivers returns a Receivers +func newReceivers(c *NotificationsV0alpha1Client, namespace string) *receivers { + return &receivers{ + client: c.RESTClient(), + ns: namespace, + } +} + +// Get takes name of the receiver, and returns the corresponding receiver object, and an error if there is any. +func (c *receivers) Get(ctx context.Context, name string, options v1.GetOptions) (result *v0alpha1.Receiver, err error) { + result = &v0alpha1.Receiver{} + err = c.client.Get(). + Namespace(c.ns). + Resource("receivers"). + Name(name). + VersionedParams(&options, scheme.ParameterCodec). + Do(ctx). + Into(result) + return +} + +// List takes label and field selectors, and returns the list of Receivers that match those selectors. +func (c *receivers) List(ctx context.Context, opts v1.ListOptions) (result *v0alpha1.ReceiverList, err error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + result = &v0alpha1.ReceiverList{} + err = c.client.Get(). + Namespace(c.ns). + Resource("receivers"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Do(ctx). + Into(result) + return +} + +// Watch returns a watch.Interface that watches the requested receivers. +func (c *receivers) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) { + var timeout time.Duration + if opts.TimeoutSeconds != nil { + timeout = time.Duration(*opts.TimeoutSeconds) * time.Second + } + opts.Watch = true + return c.client.Get(). + Namespace(c.ns). + Resource("receivers"). + VersionedParams(&opts, scheme.ParameterCodec). + Timeout(timeout). + Watch(ctx) +} + +// Create takes the representation of a receiver and creates it. Returns the server's representation of the receiver, and an error, if there is any. +func (c *receivers) Create(ctx context.Context, receiver *v0alpha1.Receiver, opts v1.CreateOptions) (result *v0alpha1.Receiver, err error) { + result = &v0alpha1.Receiver{} + err = c.client.Post(). + Namespace(c.ns). + Resource("receivers"). + VersionedParams(&opts, scheme.ParameterCodec). + Body(receiver). + Do(ctx). + Into(result) + return +} + +// Update takes the representation of a receiver and updates it. Returns the server's representation of the receiver, and an error, if there is any. +func (c *receivers) Update(ctx context.Context, receiver *v0alpha1.Receiver, opts v1.UpdateOptions) (result *v0alpha1.Receiver, err error) { + result = &v0alpha1.Receiver{} + err = c.client.Put(). + Namespace(c.ns). + Resource("receivers"). + Name(receiver.Name). + VersionedParams(&opts, scheme.ParameterCodec). + Body(receiver). + Do(ctx). + Into(result) + return +} + +// Delete takes name of the receiver and deletes it. Returns an error if one occurs. +func (c *receivers) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error { + return c.client.Delete(). + Namespace(c.ns). + Resource("receivers"). + Name(name). + Body(&opts). + Do(ctx). + Error() +} + +// DeleteCollection deletes a collection of objects. +func (c *receivers) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error { + var timeout time.Duration + if listOpts.TimeoutSeconds != nil { + timeout = time.Duration(*listOpts.TimeoutSeconds) * time.Second + } + return c.client.Delete(). + Namespace(c.ns). + Resource("receivers"). + VersionedParams(&listOpts, scheme.ParameterCodec). + Timeout(timeout). + Body(&opts). + Do(ctx). + Error() +} + +// Patch applies the patch and returns the patched receiver. +func (c *receivers) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v0alpha1.Receiver, err error) { + result = &v0alpha1.Receiver{} + err = c.client.Patch(pt). + Namespace(c.ns). + Resource("receivers"). + Name(name). + SubResource(subresources...). + VersionedParams(&opts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} + +// Apply takes the given apply declarative configuration, applies it and returns the applied receiver. +func (c *receivers) Apply(ctx context.Context, receiver *alertingnotificationsv0alpha1.ReceiverApplyConfiguration, opts v1.ApplyOptions) (result *v0alpha1.Receiver, err error) { + if receiver == nil { + return nil, fmt.Errorf("receiver provided to Apply must not be nil") + } + patchOpts := opts.ToPatchOptions() + data, err := json.Marshal(receiver) + if err != nil { + return nil, err + } + name := receiver.Name + if name == nil { + return nil, fmt.Errorf("receiver.Name must be provided to Apply") + } + result = &v0alpha1.Receiver{} + err = c.client.Patch(types.ApplyPatchType). + Namespace(c.ns). + Resource("receivers"). + Name(*name). + VersionedParams(&patchOpts, scheme.ParameterCodec). + Body(data). + Do(ctx). + Into(result) + return +} diff --git a/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/interface.go b/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/interface.go index 2095a04954a..64c33c7fc5e 100644 --- a/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/interface.go +++ b/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/interface.go @@ -10,6 +10,8 @@ import ( // Interface provides access to all the informers in this group version. type Interface interface { + // Receivers returns a ReceiverInformer. + Receivers() ReceiverInformer // TimeIntervals returns a TimeIntervalInformer. TimeIntervals() TimeIntervalInformer } @@ -25,6 +27,11 @@ func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakList return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions} } +// Receivers returns a ReceiverInformer. +func (v *version) Receivers() ReceiverInformer { + return &receiverInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} +} + // TimeIntervals returns a TimeIntervalInformer. func (v *version) TimeIntervals() TimeIntervalInformer { return &timeIntervalInformer{factory: v.factory, namespace: v.namespace, tweakListOptions: v.tweakListOptions} diff --git a/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/receiver.go b/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/receiver.go new file mode 100644 index 00000000000..194cb68a80d --- /dev/null +++ b/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/receiver.go @@ -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" +) + +// ReceiverInformer provides access to a shared informer and lister for +// Receivers. +type ReceiverInformer interface { + Informer() cache.SharedIndexInformer + Lister() v0alpha1.ReceiverLister +} + +type receiverInformer struct { + factory internalinterfaces.SharedInformerFactory + tweakListOptions internalinterfaces.TweakListOptionsFunc + namespace string +} + +// NewReceiverInformer constructs a new informer for Receiver 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 NewReceiverInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer { + return NewFilteredReceiverInformer(client, namespace, resyncPeriod, indexers, nil) +} + +// NewFilteredReceiverInformer constructs a new informer for Receiver 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 NewFilteredReceiverInformer(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().Receivers(namespace).List(context.TODO(), options) + }, + WatchFunc: func(options v1.ListOptions) (watch.Interface, error) { + if tweakListOptions != nil { + tweakListOptions(&options) + } + return client.NotificationsV0alpha1().Receivers(namespace).Watch(context.TODO(), options) + }, + }, + &alertingnotificationsv0alpha1.Receiver{}, + resyncPeriod, + indexers, + ) +} + +func (f *receiverInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer { + return NewFilteredReceiverInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions) +} + +func (f *receiverInformer) Informer() cache.SharedIndexInformer { + return f.factory.InformerFor(&alertingnotificationsv0alpha1.Receiver{}, f.defaultInformer) +} + +func (f *receiverInformer) Lister() v0alpha1.ReceiverLister { + return v0alpha1.NewReceiverLister(f.Informer().GetIndexer()) +} diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 137fbb60117..36b920d2c47 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -40,6 +40,8 @@ func (f *genericInformer) Lister() cache.GenericLister { func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) { switch resource { // 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("timeintervals"): return &genericInformer{resource: resource.GroupResource(), informer: f.Notifications().V0alpha1().TimeIntervals().Informer()}, nil diff --git a/pkg/generated/listers/alerting_notifications/v0alpha1/expansion_generated.go b/pkg/generated/listers/alerting_notifications/v0alpha1/expansion_generated.go index 65bad473129..ed4d4f239ae 100644 --- a/pkg/generated/listers/alerting_notifications/v0alpha1/expansion_generated.go +++ b/pkg/generated/listers/alerting_notifications/v0alpha1/expansion_generated.go @@ -4,6 +4,14 @@ package v0alpha1 +// ReceiverListerExpansion allows custom methods to be added to +// ReceiverLister. +type ReceiverListerExpansion interface{} + +// ReceiverNamespaceListerExpansion allows custom methods to be added to +// ReceiverNamespaceLister. +type ReceiverNamespaceListerExpansion interface{} + // TimeIntervalListerExpansion allows custom methods to be added to // TimeIntervalLister. type TimeIntervalListerExpansion interface{} diff --git a/pkg/generated/listers/alerting_notifications/v0alpha1/receiver.go b/pkg/generated/listers/alerting_notifications/v0alpha1/receiver.go new file mode 100644 index 00000000000..990dbf51ce5 --- /dev/null +++ b/pkg/generated/listers/alerting_notifications/v0alpha1/receiver.go @@ -0,0 +1,85 @@ +// 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/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// ReceiverLister helps list Receivers. +// All objects returned here must be treated as read-only. +type ReceiverLister interface { + // List lists all Receivers in the indexer. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v0alpha1.Receiver, err error) + // Receivers returns an object that can list and get Receivers. + Receivers(namespace string) ReceiverNamespaceLister + ReceiverListerExpansion +} + +// receiverLister implements the ReceiverLister interface. +type receiverLister struct { + indexer cache.Indexer +} + +// NewReceiverLister returns a new ReceiverLister. +func NewReceiverLister(indexer cache.Indexer) ReceiverLister { + return &receiverLister{indexer: indexer} +} + +// List lists all Receivers in the indexer. +func (s *receiverLister) List(selector labels.Selector) (ret []*v0alpha1.Receiver, err error) { + err = cache.ListAll(s.indexer, selector, func(m interface{}) { + ret = append(ret, m.(*v0alpha1.Receiver)) + }) + return ret, err +} + +// Receivers returns an object that can list and get Receivers. +func (s *receiverLister) Receivers(namespace string) ReceiverNamespaceLister { + return receiverNamespaceLister{indexer: s.indexer, namespace: namespace} +} + +// ReceiverNamespaceLister helps list and get Receivers. +// All objects returned here must be treated as read-only. +type ReceiverNamespaceLister interface { + // List lists all Receivers in the indexer for a given namespace. + // Objects returned here must be treated as read-only. + List(selector labels.Selector) (ret []*v0alpha1.Receiver, err error) + // Get retrieves the Receiver from the indexer for a given namespace and name. + // Objects returned here must be treated as read-only. + Get(name string) (*v0alpha1.Receiver, error) + ReceiverNamespaceListerExpansion +} + +// receiverNamespaceLister implements the ReceiverNamespaceLister +// interface. +type receiverNamespaceLister struct { + indexer cache.Indexer + namespace string +} + +// List lists all Receivers in the indexer for a given namespace. +func (s receiverNamespaceLister) List(selector labels.Selector) (ret []*v0alpha1.Receiver, err error) { + err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) { + ret = append(ret, m.(*v0alpha1.Receiver)) + }) + return ret, err +} + +// Get retrieves the Receiver from the indexer for a given namespace and name. +func (s receiverNamespaceLister) Get(name string) (*v0alpha1.Receiver, error) { + obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name) + if err != nil { + return nil, err + } + if !exists { + return nil, errors.NewNotFound(v0alpha1.Resource("receiver"), name) + } + return obj.(*v0alpha1.Receiver), nil +} diff --git a/pkg/registry/apis/alerting/notifications/receiver/authorize.go b/pkg/registry/apis/alerting/notifications/receiver/authorize.go new file mode 100644 index 00000000000..f06c6862c17 --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/receiver/authorize.go @@ -0,0 +1,53 @@ +package receiver + +import ( + "context" + + "k8s.io/apiserver/pkg/authorization/authorizer" + + "github.com/grafana/grafana/pkg/infra/appcontext" + "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 := appcontext.User(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 // TODO: Add alert.notifications.receivers:create permission + case "update": + action = accesscontrol.EvalAny( + accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsWrite), // TODO: Add alert.notifications.receivers:write permission + ) + case "deletecollection": + fallthrough + case "delete": + action = accesscontrol.EvalAny( + accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsWrite), // TODO: Add alert.notifications.receivers:delete permission + ) + } + + eval := accesscontrol.EvalAny( + accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversRead), + accesscontrol.EvalPermission(accesscontrol.ActionAlertingReceiversReadSecrets), + accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsRead), + ) + 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 +} diff --git a/pkg/registry/apis/alerting/notifications/receiver/conversions.go b/pkg/registry/apis/alerting/notifications/receiver/conversions.go new file mode 100644 index 00000000000..cd38ad5e805 --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/receiver/conversions.go @@ -0,0 +1,101 @@ +package receiver + +import ( + "fmt" + "hash/fnv" + + "github.com/prometheus/alertmanager/config" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" + + 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/services/ngalert/models" +) + +func getUID(t definitions.GettableApiReceiver) string { + sum := fnv.New64() + _, _ = sum.Write([]byte(t.Name)) + return fmt.Sprintf("%016x", sum.Sum64()) +} + +func convertToK8sResources(orgID int64, receivers []definitions.GettableApiReceiver, namespacer request.NamespaceMapper) (*model.ReceiverList, error) { + result := &model.ReceiverList{ + Items: make([]model.Receiver, 0, len(receivers)), + } + for _, receiver := range receivers { + k8sResource, err := convertToK8sResource(orgID, receiver, namespacer) + if err != nil { + return nil, err + } + result.Items = append(result.Items, *k8sResource) + } + return result, nil +} + +func convertToK8sResource(orgID int64, receiver definitions.GettableApiReceiver, namespacer request.NamespaceMapper) (*model.Receiver, error) { + spec := model.ReceiverSpec{ + Title: receiver.Receiver.Name, + } + provenance := definitions.Provenance(models.ProvenanceNone) + for _, integration := range receiver.GrafanaManagedReceivers { + if integration.Provenance != receiver.GrafanaManagedReceivers[0].Provenance { + return nil, fmt.Errorf("all integrations must have the same provenance") + } + provenance = integration.Provenance + spec.Integrations = append(spec.Integrations, model.Integration{ + Uid: &integration.UID, + Type: integration.Type, + DisableResolveMessage: &integration.DisableResolveMessage, + Settings: integration.Settings, + SecureFields: integration.SecureFields, + }) + } + + uid := getUID(receiver) // TODO replace to stable UID when we switch to normal storage + return &model.Receiver{ + TypeMeta: resourceInfo.TypeMeta(), + ObjectMeta: metav1.ObjectMeta{ + UID: types.UID(uid), // This is needed to make PATCH work + Name: uid, // TODO replace to stable UID when we switch to normal storage + Namespace: namespacer(orgID), + Annotations: map[string]string{ // TODO find a better place for provenance? + "grafana.com/provenance": string(provenance), + }, + ResourceVersion: "", // TODO: Implement optimistic concurrency. + }, + Spec: spec, + }, nil +} + +func convertToDomainModel(receiver *model.Receiver) (definitions.GettableApiReceiver, error) { + // TODO: Using GettableApiReceiver instead of PostableApiReceiver so that SecureFields type matches. + gettable := definitions.GettableApiReceiver{ + Receiver: config.Receiver{ + Name: receiver.Spec.Title, + }, + GettableGrafanaReceivers: definitions.GettableGrafanaReceivers{ + GrafanaManagedReceivers: []*definitions.GettableGrafanaReceiver{}, + }, + } + + for _, integration := range receiver.Spec.Integrations { + grafanaIntegration := definitions.GettableGrafanaReceiver{ + Name: receiver.Spec.Title, + Type: integration.Type, + Settings: integration.Settings, + SecureFields: integration.SecureFields, + //Provenance: "", //TODO: Convert provenance? + } + if integration.Uid != nil { + grafanaIntegration.UID = *integration.Uid + } + if integration.DisableResolveMessage != nil { + grafanaIntegration.DisableResolveMessage = *integration.DisableResolveMessage + } + gettable.GettableGrafanaReceivers.GrafanaManagedReceivers = append(gettable.GettableGrafanaReceivers.GrafanaManagedReceivers, &grafanaIntegration) + } + + return gettable, nil +} diff --git a/pkg/registry/apis/alerting/notifications/receiver/legacy_storage.go b/pkg/registry/apis/alerting/notifications/receiver/legacy_storage.go new file mode 100644 index 00000000000..8d7d7b641f4 --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/receiver/legacy_storage.go @@ -0,0 +1,225 @@ +package receiver + +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" + + "github.com/grafana/grafana/pkg/apimachinery/identity" + 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" + "github.com/grafana/grafana/pkg/services/ngalert/models" +) + +var ( + _ grafanaRest.LegacyStorage = (*legacyStorage)(nil) +) + +var resourceInfo = notifications.ReceiverResourceInfo + +type ReceiverService interface { + GetReceiver(ctx context.Context, q models.GetReceiverQuery, user identity.Requester) (definitions.GettableApiReceiver, error) + GetReceivers(ctx context.Context, q models.GetReceiversQuery, user identity.Requester) ([]definitions.GettableApiReceiver, error) + CreateReceiver(ctx context.Context, r definitions.GettableApiReceiver, orgID int64) (definitions.GettableApiReceiver, error) // TODO: Uses Gettable for Write, consider creating new struct. + UpdateReceiver(ctx context.Context, r definitions.GettableApiReceiver, orgID int64) (definitions.GettableApiReceiver, error) // TODO: Uses Gettable for Write, consider creating new struct. + DeleteReceiver(ctx context.Context, name string, orgID int64, provenance definitions.Provenance, version string) error +} + +type legacyStorage struct { + service ReceiverService + 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) List(ctx context.Context, _ *internalversion.ListOptions) (runtime.Object, error) { + orgId, err := request.OrgIDForList(ctx) + if err != nil { + return nil, err + } + + q := models.GetReceiversQuery{ + OrgID: orgId, + //Names: ctx.QueryStrings("names"), // TODO: Query params. + //Limit: ctx.QueryInt("limit"), + //Offset: ctx.QueryInt("offset"), + //Decrypt: ctx.QueryBool("decrypt"), + } + + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, err + } + + res, err := s.service.GetReceivers(ctx, q, user) + if err != nil { + return nil, err + } + + return convertToK8sResources(orgId, res, s.namespacer) +} + +func (s *legacyStorage) Get(ctx context.Context, uid string, _ *metav1.GetOptions) (runtime.Object, error) { + info, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + + q := models.GetReceiverQuery{ + OrgID: info.OrgID, + Name: uid, // TODO: Name/UID mapping or change signature of service. + //Decrypt: ctx.QueryBool("decrypt"), // TODO: Query params. + } + + user, err := identity.GetRequester(ctx) + if err != nil { + return nil, err + } + + res, err := s.service.GetReceiver(ctx, q, user) + if err != nil { + return nil, err + } + + return convertToK8sResource(info.OrgID, res, s.namespacer) +} + +func (s *legacyStorage) Create(ctx context.Context, + obj runtime.Object, + createValidation rest.ValidateObjectFunc, + _ *metav1.CreateOptions, +) (runtime.Object, error) { + info, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, err + } + if createValidation != nil { + if err := createValidation(ctx, obj.DeepCopyObject()); err != nil { + return nil, err + } + } + p, ok := obj.(*notifications.Receiver) + if !ok { + return nil, fmt.Errorf("expected receiver but got %s", obj.GetObjectKind().GroupVersionKind()) + } + if p.ObjectMeta.Name != "" { // TODO remove when metadata.name can be defined by user + return nil, errors.NewBadRequest("object's metadata.name should be empty") + } + model, err := convertToDomainModel(p) + if err != nil { + return nil, err + } + out, err := s.service.CreateReceiver(ctx, model, info.OrgID) + if err != nil { + return nil, err + } + return convertToK8sResource(info.OrgID, out, s.namespacer) +} + +func (s *legacyStorage) Update(ctx context.Context, + uid string, + objInfo rest.UpdatedObjectInfo, + createValidation rest.ValidateObjectFunc, + updateValidation rest.ValidateObjectUpdateFunc, + _ bool, + _ *metav1.UpdateOptions, +) (runtime.Object, bool, error) { + info, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, false, err + } + + old, err := s.Get(ctx, uid, 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.Receiver) + if !ok { + return nil, false, fmt.Errorf("expected receiver but got %s", obj.GetObjectKind().GroupVersionKind()) + } + model, err := convertToDomainModel(p) + if err != nil { + return old, false, err + } + + if p.ObjectMeta.Name != getUID(model) { + return nil, false, errors.NewBadRequest("title cannot be changed. Consider creating a new resource.") + } + + updated, err := s.service.UpdateReceiver(ctx, model, info.OrgID) + if err != nil { + return nil, false, err + } + + r, err := convertToK8sResource(info.OrgID, updated, s.namespacer) + return r, false, err +} + +// GracefulDeleter +func (s *legacyStorage) Delete(ctx context.Context, uid string, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions) (runtime.Object, bool, error) { + info, err := request.NamespaceInfoFrom(ctx, true) + if err != nil { + return nil, false, err + } + old, err := s.Get(ctx, uid, nil) + if err != nil { + return old, false, err + } + if deleteValidation != nil { + if err = deleteValidation(ctx, old); err != nil { + return nil, false, err + } + } + version := "" + if options.Preconditions != nil && options.Preconditions.ResourceVersion != nil { + version = *options.Preconditions.ResourceVersion + } + p, ok := old.(*notifications.Receiver) + if !ok { + return nil, false, fmt.Errorf("expected receiver but got %s", old.GetObjectKind().GroupVersionKind()) + } + + err = s.service.DeleteReceiver(ctx, p.Spec.Title, info.OrgID, definitions.Provenance(models.ProvenanceNone), version) // TODO add support for dry-run option + return old, false, err // false - will be deleted async +} + +func (s *legacyStorage) DeleteCollection(ctx context.Context, deleteValidation rest.ValidateObjectFunc, options *metav1.DeleteOptions, listOptions *internalversion.ListOptions) (runtime.Object, error) { + return nil, errors.NewMethodNotSupported(resourceInfo.GroupResource(), "deleteCollection") +} diff --git a/pkg/registry/apis/alerting/notifications/receiver/storage.go b/pkg/registry/apis/alerting/notifications/receiver/storage.go new file mode 100644 index 00000000000..80ec2d0bf9b --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/receiver/storage.go @@ -0,0 +1,80 @@ +package receiver + +import ( + "fmt" + + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/registry/generic" + genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" + "k8s.io/apiserver/pkg/registry/rest" + + "github.com/prometheus/client_golang/prometheus" + + model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" + grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic" + grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" + "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" + "github.com/grafana/grafana/pkg/services/apiserver/utils" +) + +var _ grafanarest.Storage = (*storage)(nil) + +type storage struct { + *genericregistry.Store +} + +func (s storage) Compare(storageObj, legacyObj runtime.Object) bool { + // TODO implement when supported dual write mode is not Mode0 + return false +} + +func NewStorage( + legacySvc ReceiverService, + namespacer request.NamespaceMapper, + scheme *runtime.Scheme, + desiredMode grafanarest.DualWriterMode, + optsGetter generic.RESTOptionsGetter, + reg prometheus.Registerer) (rest.Storage, error) { + legacyStore := &legacyStorage{ + service: legacySvc, + namespacer: namespacer, + tableConverter: utils.NewTableConverter( + resourceInfo.GroupResource(), + []metav1.TableColumnDefinition{ + {Name: "Name", Type: "string", Format: "name"}, + {Name: "Title", Type: "string", Format: "string", Description: "The receiver name"}, // TODO: Add integration types. + }, + func(obj any) ([]interface{}, error) { + r, ok := obj.(*model.Receiver) + if ok { + return []interface{}{ + r.Name, + r.Spec.Title, + // r.Spec, //TODO implement formatting for Spec, same as UI? + }, nil + } + return nil, fmt.Errorf("expected resource or info") + }), + } + if optsGetter != nil && desiredMode != grafanarest.Mode0 { + strategy := grafanaregistry.NewStrategy(scheme) + s := &genericregistry.Store{ + NewFunc: resourceInfo.NewFunc, + NewListFunc: resourceInfo.NewListFunc, + PredicateFunc: grafanaregistry.Matcher, + DefaultQualifiedResource: resourceInfo.GroupResource(), + SingularQualifiedResource: resourceInfo.SingularGroupResource(), + TableConvertor: legacyStore.tableConverter, + CreateStrategy: strategy, + UpdateStrategy: strategy, + DeleteStrategy: strategy, + } + options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: grafanaregistry.GetAttrs} + if err := s.CompleteWithOptions(options); err != nil { + return nil, err + } + return grafanarest.NewDualWriter(desiredMode, legacyStore, storage{Store: s}, reg), nil + } + return legacyStore, nil +} diff --git a/pkg/registry/apis/alerting/notifications/register.go b/pkg/registry/apis/alerting/notifications/register.go index cb09efaa456..459e6451e82 100644 --- a/pkg/registry/apis/alerting/notifications/register.go +++ b/pkg/registry/apis/alerting/notifications/register.go @@ -18,6 +18,7 @@ import ( notificationsModels "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" "github.com/grafana/grafana/pkg/apiserver/builder" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" + receiver "github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/receiver" timeInterval "github.com/grafana/grafana/pkg/registry/apis/alerting/notifications/timeinterval" "github.com/grafana/grafana/pkg/services/accesscontrol" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" @@ -86,8 +87,14 @@ func (t NotificationsAPIBuilder) GetAPIGroupInfo( return nil, fmt.Errorf("failed to initialize time-interval storage: %w", err) } + recvStorage, err := receiver.NewStorage(nil, t.namespacer, scheme, desiredMode, optsGetter, reg) // TODO: add receiver service + if err != nil { + return nil, fmt.Errorf("failed to initialize receiver storage: %w", err) + } + apiGroupInfo.VersionedResourcesStorageMap[notificationsModels.VERSION] = map[string]rest.Storage{ notificationsModels.TimeIntervalResourceInfo.StoragePath(): intervals, + notificationsModels.ReceiverResourceInfo.StoragePath(): recvStorage, } return &apiGroupInfo, nil } @@ -106,6 +113,8 @@ func (t NotificationsAPIBuilder) GetAuthorizer() authorizer.Authorizer { switch a.GetResource() { case notificationsModels.TimeIntervalResourceInfo.GroupResource().Resource: return timeInterval.Authorize(ctx, t.authz, a) + case notificationsModels.ReceiverResourceInfo.GroupResource().Resource: + return receiver.Authorize(ctx, t.authz, a) } return authorizer.DecisionNoOpinion, "", nil })