mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
Merge remote-tracking branch 'origin/main' into resource-store
This commit is contained in:
commit
b07c4e4210
1
.github/CODEOWNERS
vendored
1
.github/CODEOWNERS
vendored
@ -71,6 +71,7 @@
|
||||
/scripts/modowners/ @grafana/grafana-backend-services-squad
|
||||
/hack/ @grafana/grafana-app-platform-squad
|
||||
|
||||
/apps/alerting/ @grafana/alerting-backend
|
||||
/pkg/api/ @grafana/grafana-backend-group
|
||||
/pkg/apis/ @grafana/grafana-app-platform-squad
|
||||
/pkg/bus/ @grafana/grafana-search-and-storage
|
||||
|
1
apps/alerting/notifications/cue.mod/module.cue
Normal file
1
apps/alerting/notifications/cue.mod/module.cue
Normal file
@ -0,0 +1 @@
|
||||
module:"notifications"
|
37
apps/alerting/notifications/timeInterval.cue
Normal file
37
apps/alerting/notifications/timeInterval.cue
Normal file
@ -0,0 +1,37 @@
|
||||
package core
|
||||
|
||||
timeInterval: {
|
||||
kind: "TimeInterval"
|
||||
group: "notifications"
|
||||
apiResource: {
|
||||
groupOverride: "notifications.alerting.grafana.app"
|
||||
}
|
||||
codegen: {
|
||||
frontend: false
|
||||
backend: true
|
||||
}
|
||||
pluralName: "TimeIntervals"
|
||||
current: "v0alpha1"
|
||||
versions: {
|
||||
"v0alpha1": {
|
||||
schema: {
|
||||
#TimeRange: {
|
||||
start_time: string
|
||||
end_time: string
|
||||
}
|
||||
#Interval: {
|
||||
times?: [...#TimeRange]
|
||||
weekdays?: [...string]
|
||||
days_of_month?: [...string]
|
||||
months?: [...string]
|
||||
years?: [...string]
|
||||
location?: string
|
||||
}
|
||||
spec: {
|
||||
name: string
|
||||
time_intervals: [...#Interval]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1304,6 +1304,9 @@ disable_jitter = false
|
||||
# Retention period for Alertmanager notification log entries.
|
||||
notification_log_retention = 5d
|
||||
|
||||
# Duration for which a resolved alert state transition will continue to be sent to the Alertmanager.
|
||||
resolved_alert_retention = 15m
|
||||
|
||||
[unified_alerting.screenshots]
|
||||
# Enable screenshots in notifications. You must have either installed the Grafana image rendering
|
||||
# plugin, or set up Grafana to use a remote rendering service.
|
||||
|
@ -792,7 +792,7 @@
|
||||
;role_attribute_strict = false
|
||||
;groups_attribute_path =
|
||||
;id_token_attribute_name =
|
||||
;team_ids_attribute_path
|
||||
;team_ids_attribute_path
|
||||
;auth_url = https://foo.bar/login/oauth/authorize
|
||||
;token_url = https://foo.bar/login/oauth/access_token
|
||||
;api_url = https://foo.bar/user
|
||||
@ -1290,6 +1290,9 @@
|
||||
# Retention period for Alertmanager notification log entries.
|
||||
;notification_log_retention = 5d
|
||||
|
||||
# Duration for which a resolved alert state transition will continue to be sent to the Alertmanager.
|
||||
;resolved_alert_retention = 15m
|
||||
|
||||
[unified_alerting.screenshots]
|
||||
# Enable screenshots in notifications. You must have either installed the Grafana image rendering
|
||||
# plugin, or set up Grafana to use a remote rendering service.
|
||||
@ -1837,4 +1840,4 @@ timeout = 30s
|
||||
#################################### Public Dashboards #####################################
|
||||
[public_dashboards]
|
||||
# Set to false to disable public dashboards
|
||||
;enabled = true
|
||||
;enabled = true
|
||||
|
@ -191,6 +191,7 @@ Experimental features might be changed or removed without prior notice.
|
||||
| `pinNavItems` | Enables pinning of nav items |
|
||||
| `failWrongDSUID` | Throws an error if a datasource has an invalid UIDs |
|
||||
| `databaseReadReplica` | Use a read replica for some database queries. |
|
||||
| `alertingApiServer` | Register Alerting APIs with the K8s API server |
|
||||
|
||||
## Development feature toggles
|
||||
|
||||
|
@ -22,8 +22,13 @@ go mod download
|
||||
|
||||
# the happy path
|
||||
./hack/update-codegen.sh
|
||||
|
||||
```
|
||||
|
||||
Note that the script deletes existing openapi go code and regenerates in place so that you will temporarily see
|
||||
deleted files in your `git status`. After a successful run, you should see them restored.
|
||||
```
|
||||
|
||||
If resource client is not generated for your resource make sure that it follows the k8s guidelines for structuring the resource definition
|
||||
|
||||
- the directory is named after resource version, i.e. `<resource_name>/v<version>` (e.g. service/v0alpha1)
|
||||
- the resource directory contains file `types.go` that includes resource definitions
|
||||
- the resource definitions are annotated with comment `// +genclient`
|
||||
|
@ -198,4 +198,5 @@ export interface FeatureToggles {
|
||||
databaseReadReplica?: boolean;
|
||||
zanzana?: boolean;
|
||||
passScopeToDashboardApi?: boolean;
|
||||
alertingApiServer?: boolean;
|
||||
}
|
||||
|
6
pkg/apis/alerting_notifications/v0alpha1/doc.go
Normal file
6
pkg/apis/alerting_notifications/v0alpha1/doc.go
Normal file
@ -0,0 +1,6 @@
|
||||
// +k8s:deepcopy-gen=package
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:defaulter-gen=TypeMeta
|
||||
// +groupName=notifications.alerting.grafana.app
|
||||
|
||||
package v0alpha1 // import "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
53
pkg/apis/alerting_notifications/v0alpha1/register.go
Normal file
53
pkg/apis/alerting_notifications/v0alpha1/register.go
Normal file
@ -0,0 +1,53 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
func init() {
|
||||
localSchemeBuilder.Register(AddKnownTypes)
|
||||
}
|
||||
|
||||
const (
|
||||
GROUP = "notifications.alerting.grafana.app"
|
||||
VERSION = "v0alpha1"
|
||||
APIVERSION = GROUP + "/" + VERSION
|
||||
)
|
||||
|
||||
var (
|
||||
TimeIntervalResourceInfo = common.NewResourceInfo(GROUP, VERSION,
|
||||
"timeintervals", "timeinterval", "TimeIntervals",
|
||||
func() runtime.Object { return &TimeInterval{} },
|
||||
func() runtime.Object { return &TimeIntervalList{} },
|
||||
)
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
SchemeGroupVersion = schema.GroupVersion{Group: GROUP, Version: VERSION}
|
||||
// SchemaBuilder is used by standard codegen
|
||||
SchemeBuilder runtime.SchemeBuilder
|
||||
localSchemeBuilder = &SchemeBuilder
|
||||
AddToScheme = localSchemeBuilder.AddToScheme
|
||||
)
|
||||
|
||||
// Adds the list of known types to the given scheme.
|
||||
func AddKnownTypes(scheme *runtime.Scheme) error {
|
||||
return AddKnownTypesGroup(scheme, SchemeGroupVersion)
|
||||
}
|
||||
|
||||
// Adds the list of known types to the given scheme and group version.
|
||||
func AddKnownTypesGroup(scheme *runtime.Scheme, g schema.GroupVersion) error {
|
||||
scheme.AddKnownTypes(g,
|
||||
&TimeInterval{},
|
||||
&TimeIntervalList{},
|
||||
)
|
||||
metav1.AddToGroupVersion(scheme, g)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
||||
}
|
115
pkg/apis/alerting_notifications/v0alpha1/testing.go
Normal file
115
pkg/apis/alerting_notifications/v0alpha1/testing.go
Normal file
@ -0,0 +1,115 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/rand"
|
||||
"slices"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
// +k8s:openapi-gen=false
|
||||
// +k8s:deepcopy-gen=false
|
||||
type IntervalMutator func(spec *Interval)
|
||||
|
||||
// +k8s:openapi-gen=false
|
||||
// +k8s:deepcopy-gen=false
|
||||
type IntervalGenerator struct {
|
||||
mutators []IntervalMutator
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) With(mutators ...IntervalMutator) IntervalGenerator {
|
||||
return IntervalGenerator{
|
||||
mutators: append(t.mutators, mutators...),
|
||||
}
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) generateDaysOfMonth() string {
|
||||
isRange := rand.Int()%2 == 0
|
||||
if !isRange {
|
||||
return fmt.Sprintf("%d", rand.Intn(30)+1)
|
||||
}
|
||||
from := rand.Intn(15) + 1
|
||||
to := rand.Intn(31-from) + from + 1
|
||||
return fmt.Sprintf("%d:%d", from, to)
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) generateTimeRange() TimeRange {
|
||||
from := rand.Int63n(1440 / 2) // [0, 719]
|
||||
to := from + rand.Int63n(1440/2) + 1 // from < ([0,719] + [1,720]) < 1440
|
||||
return TimeRange{
|
||||
StartTime: time.Unix(from*60, 0).UTC().Format("15:04"),
|
||||
EndTime: time.Unix(to*60, 0).UTC().Format("15:04"),
|
||||
}
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) generateWeekday() string {
|
||||
day := rand.Intn(7)
|
||||
return strings.ToLower(time.Weekday(day).String())
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) generateYear() string {
|
||||
from := 1970 + rand.Intn(100)
|
||||
if rand.Int()%3 == 0 {
|
||||
to := 1970 + from + rand.Intn(10) + 1
|
||||
return fmt.Sprintf("%d:%d", from, to)
|
||||
}
|
||||
return fmt.Sprintf("%d", from)
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) generateLocation() *string {
|
||||
if rand.Int()%3 == 0 {
|
||||
return nil
|
||||
}
|
||||
return util.Pointer("UTC")
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) generateMonth() string {
|
||||
return fmt.Sprintf("%d", rand.Intn(12)+1)
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) GenerateMany(count int) []Interval {
|
||||
result := make([]Interval, 0, count)
|
||||
for i := 0; i < count; i++ {
|
||||
result = append(result, t.Generate())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (t IntervalGenerator) Generate() Interval {
|
||||
i := Interval{
|
||||
DaysOfMonth: generateMany(rand.Intn(6), true, t.generateDaysOfMonth),
|
||||
Location: t.generateLocation(),
|
||||
Months: generateMany(rand.Intn(3), true, t.generateMonth),
|
||||
Times: generateMany(rand.Intn(6), true, t.generateTimeRange),
|
||||
Weekdays: generateMany(rand.Intn(3), true, t.generateWeekday),
|
||||
Years: generateMany(rand.Intn(3), true, t.generateYear),
|
||||
}
|
||||
for _, mutator := range t.mutators {
|
||||
mutator(&i)
|
||||
}
|
||||
return i
|
||||
}
|
||||
|
||||
func generateMany[T comparable](repeatTimes int, unique bool, f func() T) []T {
|
||||
qty := repeatTimes + 1
|
||||
result := make([]T, 0, qty)
|
||||
for i := 0; i < qty; i++ {
|
||||
r := f()
|
||||
if unique && slices.Contains(result, r) {
|
||||
continue
|
||||
}
|
||||
result = append(result, f())
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func CopyWith(in Interval, mutators ...IntervalMutator) Interval {
|
||||
r := *in.DeepCopy()
|
||||
for _, mut := range mutators {
|
||||
mut(&r)
|
||||
}
|
||||
return r
|
||||
}
|
@ -0,0 +1,38 @@
|
||||
package v0alpha1
|
||||
|
||||
// Interval defines model for Interval.
|
||||
// +k8s:openapi-gen=true
|
||||
type Interval struct {
|
||||
// +listType=atomic
|
||||
DaysOfMonth []string `json:"days_of_month,omitempty"`
|
||||
|
||||
// +listType=atomic
|
||||
Location *string `json:"location,omitempty"`
|
||||
|
||||
// +listType=atomic
|
||||
Months []string `json:"months,omitempty"`
|
||||
|
||||
// +listType=atomic
|
||||
Times []TimeRange `json:"times,omitempty"`
|
||||
|
||||
// +listType=atomic
|
||||
Weekdays []string `json:"weekdays,omitempty"`
|
||||
|
||||
// +listType=atomic
|
||||
Years []string `json:"years,omitempty"`
|
||||
}
|
||||
|
||||
// Spec defines model for Spec.
|
||||
// +k8s:openapi-gen=true
|
||||
type TimeIntervalSpec struct {
|
||||
Name string `json:"name"`
|
||||
// +listType=atomic
|
||||
TimeIntervals []Interval `json:"time_intervals"`
|
||||
}
|
||||
|
||||
// TimeRange defines model for TimeRange.
|
||||
// +k8s:openapi-gen=true
|
||||
type TimeRange struct {
|
||||
EndTime string `json:"end_time"`
|
||||
StartTime string `json:"start_time"`
|
||||
}
|
87
pkg/apis/alerting_notifications/v0alpha1/types.go
Normal file
87
pkg/apis/alerting_notifications/v0alpha1/types.go
Normal file
@ -0,0 +1,87 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
// +genclient
|
||||
// +k8s:openapi-gen=true
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type TimeInterval struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
Spec TimeIntervalSpec `json:"spec"`
|
||||
}
|
||||
|
||||
func (o *TimeInterval) GetSpec() any {
|
||||
return o.Spec
|
||||
}
|
||||
|
||||
func (o *TimeInterval) SetSpec(spec any) error {
|
||||
cast, ok := spec.(TimeIntervalSpec)
|
||||
if !ok {
|
||||
return fmt.Errorf("cannot set spec type %#v, not of type Spec", spec)
|
||||
}
|
||||
o.Spec = cast
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *TimeInterval) GetCreatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/createdBy"]
|
||||
}
|
||||
|
||||
func (o *TimeInterval) SetCreatedBy(createdBy string) {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
o.ObjectMeta.Annotations["grafana.com/createdBy"] = createdBy
|
||||
}
|
||||
|
||||
func (o *TimeInterval) 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 *TimeInterval) 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 *TimeInterval) GetUpdatedBy() string {
|
||||
if o.ObjectMeta.Annotations == nil {
|
||||
o.ObjectMeta.Annotations = make(map[string]string)
|
||||
}
|
||||
|
||||
return o.ObjectMeta.Annotations["grafana.com/updatedBy"]
|
||||
}
|
||||
|
||||
func (o *TimeInterval) 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 TimeIntervalList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
Items []TimeInterval `json:"items"`
|
||||
}
|
@ -0,0 +1,157 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
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 *Interval) DeepCopyInto(out *Interval) {
|
||||
*out = *in
|
||||
if in.DaysOfMonth != nil {
|
||||
in, out := &in.DaysOfMonth, &out.DaysOfMonth
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Location != nil {
|
||||
in, out := &in.Location, &out.Location
|
||||
*out = new(string)
|
||||
**out = **in
|
||||
}
|
||||
if in.Months != nil {
|
||||
in, out := &in.Months, &out.Months
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Times != nil {
|
||||
in, out := &in.Times, &out.Times
|
||||
*out = make([]TimeRange, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Weekdays != nil {
|
||||
in, out := &in.Weekdays, &out.Weekdays
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Years != nil {
|
||||
in, out := &in.Years, &out.Years
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Interval.
|
||||
func (in *Interval) DeepCopy() *Interval {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Interval)
|
||||
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
|
||||
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 TimeInterval.
|
||||
func (in *TimeInterval) DeepCopy() *TimeInterval {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TimeInterval)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TimeInterval) 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 *TimeIntervalList) DeepCopyInto(out *TimeIntervalList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]TimeInterval, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TimeIntervalList.
|
||||
func (in *TimeIntervalList) DeepCopy() *TimeIntervalList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TimeIntervalList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *TimeIntervalList) 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 *TimeIntervalSpec) DeepCopyInto(out *TimeIntervalSpec) {
|
||||
*out = *in
|
||||
if in.TimeIntervals != nil {
|
||||
in, out := &in.TimeIntervals, &out.TimeIntervals
|
||||
*out = make([]Interval, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TimeIntervalSpec.
|
||||
func (in *TimeIntervalSpec) DeepCopy() *TimeIntervalSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TimeIntervalSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *TimeRange) DeepCopyInto(out *TimeRange) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TimeRange.
|
||||
func (in *TimeRange) DeepCopy() *TimeRange {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(TimeRange)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
// All generated defaulters are covering - they call all nested defaulters.
|
||||
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||
return nil
|
||||
}
|
303
pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go
Normal file
303
pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go
Normal file
@ -0,0 +1,303 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by openapi-gen. DO NOT EDIT.
|
||||
|
||||
// This file was autogenerated by openapi-gen. Do not edit it manually!
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
common "k8s.io/kube-openapi/pkg/common"
|
||||
spec "k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||
return map[string]common.OpenAPIDefinition{
|
||||
"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.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),
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TimeRange": schema_pkg_apis_alerting_notifications_v0alpha1_TimeRange(ref),
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_Interval(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Interval defines model for Interval.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"days_of_month": {
|
||||
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: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"location": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-type": "atomic",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"months": {
|
||||
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: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"times": {
|
||||
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.TimeRange"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"weekdays": {
|
||||
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: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
"years": {
|
||||
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: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TimeRange"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_TimeInterval(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.TimeIntervalSpec"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "spec"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TimeIntervalSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_TimeIntervalList(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.TimeInterval"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.TimeInterval", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_TimeIntervalSpec(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{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"time_intervals": {
|
||||
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.Interval"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "time_intervals"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1.Interval"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_alerting_notifications_v0alpha1_TimeRange(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "TimeRange defines model for TimeRange.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"end_time": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"start_time": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"end_time", "start_time"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
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
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1,TimeRange,StartTime
|
@ -0,0 +1,83 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// IntervalApplyConfiguration represents an declarative configuration of the Interval type for use
|
||||
// with apply.
|
||||
type IntervalApplyConfiguration struct {
|
||||
DaysOfMonth []string `json:"days_of_month,omitempty"`
|
||||
Location *string `json:"location,omitempty"`
|
||||
Months []string `json:"months,omitempty"`
|
||||
Times []TimeRangeApplyConfiguration `json:"times,omitempty"`
|
||||
Weekdays []string `json:"weekdays,omitempty"`
|
||||
Years []string `json:"years,omitempty"`
|
||||
}
|
||||
|
||||
// IntervalApplyConfiguration constructs an declarative configuration of the Interval type for use with
|
||||
// apply.
|
||||
func Interval() *IntervalApplyConfiguration {
|
||||
return &IntervalApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithDaysOfMonth adds the given value to the DaysOfMonth 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 DaysOfMonth field.
|
||||
func (b *IntervalApplyConfiguration) WithDaysOfMonth(values ...string) *IntervalApplyConfiguration {
|
||||
for i := range values {
|
||||
b.DaysOfMonth = append(b.DaysOfMonth, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithLocation sets the Location 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 Location field is set to the value of the last call.
|
||||
func (b *IntervalApplyConfiguration) WithLocation(value string) *IntervalApplyConfiguration {
|
||||
b.Location = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMonths adds the given value to the Months 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 Months field.
|
||||
func (b *IntervalApplyConfiguration) WithMonths(values ...string) *IntervalApplyConfiguration {
|
||||
for i := range values {
|
||||
b.Months = append(b.Months, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTimes adds the given value to the Times 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 Times field.
|
||||
func (b *IntervalApplyConfiguration) WithTimes(values ...*TimeRangeApplyConfiguration) *IntervalApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithTimes")
|
||||
}
|
||||
b.Times = append(b.Times, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithWeekdays adds the given value to the Weekdays 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 Weekdays field.
|
||||
func (b *IntervalApplyConfiguration) WithWeekdays(values ...string) *IntervalApplyConfiguration {
|
||||
for i := range values {
|
||||
b.Weekdays = append(b.Weekdays, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithYears adds the given value to the Years 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 Years field.
|
||||
func (b *IntervalApplyConfiguration) WithYears(values ...string) *IntervalApplyConfiguration {
|
||||
for i := range values {
|
||||
b.Years = append(b.Years, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
@ -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"
|
||||
)
|
||||
|
||||
// TimeIntervalApplyConfiguration represents an declarative configuration of the TimeInterval type for use
|
||||
// with apply.
|
||||
type TimeIntervalApplyConfiguration struct {
|
||||
v1.TypeMetaApplyConfiguration `json:",inline"`
|
||||
*v1.ObjectMetaApplyConfiguration `json:"metadata,omitempty"`
|
||||
Spec *TimeIntervalSpecApplyConfiguration `json:"spec,omitempty"`
|
||||
}
|
||||
|
||||
// TimeInterval constructs an declarative configuration of the TimeInterval type for use with
|
||||
// apply.
|
||||
func TimeInterval(name, namespace string) *TimeIntervalApplyConfiguration {
|
||||
b := &TimeIntervalApplyConfiguration{}
|
||||
b.WithName(name)
|
||||
b.WithNamespace(namespace)
|
||||
b.WithKind("TimeInterval")
|
||||
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 *TimeIntervalApplyConfiguration) WithKind(value string) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithAPIVersion(value string) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithName(value string) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithGenerateName(value string) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithNamespace(value string) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithUID(value types.UID) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithResourceVersion(value string) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithGeneration(value int64) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithCreationTimestamp(value metav1.Time) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithDeletionTimestamp(value metav1.Time) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithDeletionGracePeriodSeconds(value int64) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithLabels(entries map[string]string) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithAnnotations(entries map[string]string) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithOwnerReferences(values ...*v1.OwnerReferenceApplyConfiguration) *TimeIntervalApplyConfiguration {
|
||||
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 *TimeIntervalApplyConfiguration) WithFinalizers(values ...string) *TimeIntervalApplyConfiguration {
|
||||
b.ensureObjectMetaApplyConfigurationExists()
|
||||
for i := range values {
|
||||
b.Finalizers = append(b.Finalizers, values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
func (b *TimeIntervalApplyConfiguration) 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 *TimeIntervalApplyConfiguration) WithSpec(value *TimeIntervalSpecApplyConfiguration) *TimeIntervalApplyConfiguration {
|
||||
b.Spec = value
|
||||
return b
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// TimeIntervalSpecApplyConfiguration represents an declarative configuration of the TimeIntervalSpec type for use
|
||||
// with apply.
|
||||
type TimeIntervalSpecApplyConfiguration struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
TimeIntervals []IntervalApplyConfiguration `json:"time_intervals,omitempty"`
|
||||
}
|
||||
|
||||
// TimeIntervalSpecApplyConfiguration constructs an declarative configuration of the TimeIntervalSpec type for use with
|
||||
// apply.
|
||||
func TimeIntervalSpec() *TimeIntervalSpecApplyConfiguration {
|
||||
return &TimeIntervalSpecApplyConfiguration{}
|
||||
}
|
||||
|
||||
// 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 *TimeIntervalSpecApplyConfiguration) WithName(value string) *TimeIntervalSpecApplyConfiguration {
|
||||
b.Name = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithTimeIntervals adds the given value to the TimeIntervals 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 TimeIntervals field.
|
||||
func (b *TimeIntervalSpecApplyConfiguration) WithTimeIntervals(values ...*IntervalApplyConfiguration) *TimeIntervalSpecApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithTimeIntervals")
|
||||
}
|
||||
b.TimeIntervals = append(b.TimeIntervals, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// TimeRangeApplyConfiguration represents an declarative configuration of the TimeRange type for use
|
||||
// with apply.
|
||||
type TimeRangeApplyConfiguration struct {
|
||||
EndTime *string `json:"end_time,omitempty"`
|
||||
StartTime *string `json:"start_time,omitempty"`
|
||||
}
|
||||
|
||||
// TimeRangeApplyConfiguration constructs an declarative configuration of the TimeRange type for use with
|
||||
// apply.
|
||||
func TimeRange() *TimeRangeApplyConfiguration {
|
||||
return &TimeRangeApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithEndTime sets the EndTime 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 EndTime field is set to the value of the last call.
|
||||
func (b *TimeRangeApplyConfiguration) WithEndTime(value string) *TimeRangeApplyConfiguration {
|
||||
b.EndTime = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithStartTime sets the StartTime 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 StartTime field is set to the value of the last call.
|
||||
func (b *TimeRangeApplyConfiguration) WithStartTime(value string) *TimeRangeApplyConfiguration {
|
||||
b.StartTime = &value
|
||||
return b
|
||||
}
|
@ -5,8 +5,10 @@
|
||||
package applyconfiguration
|
||||
|
||||
import (
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
|
||||
servicev0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/service/v0alpha1"
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
|
||||
alertingnotificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1"
|
||||
applyconfigurationservicev0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/service/v0alpha1"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
@ -14,11 +16,21 @@ import (
|
||||
// apply configuration type exists for the given GroupVersionKind.
|
||||
func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
switch kind {
|
||||
// Group=service.grafana.app, Version=v0alpha1
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("ExternalName"):
|
||||
return &servicev0alpha1.ExternalNameApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("ExternalNameSpec"):
|
||||
return &servicev0alpha1.ExternalNameSpecApplyConfiguration{}
|
||||
// Group=notifications.alerting.grafana.app, Version=v0alpha1
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("Interval"):
|
||||
return &alertingnotificationsv0alpha1.IntervalApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("TimeInterval"):
|
||||
return &alertingnotificationsv0alpha1.TimeIntervalApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("TimeIntervalSpec"):
|
||||
return &alertingnotificationsv0alpha1.TimeIntervalSpecApplyConfiguration{}
|
||||
case v0alpha1.SchemeGroupVersion.WithKind("TimeRange"):
|
||||
return &alertingnotificationsv0alpha1.TimeRangeApplyConfiguration{}
|
||||
|
||||
// Group=service.grafana.app, Version=v0alpha1
|
||||
case servicev0alpha1.SchemeGroupVersion.WithKind("ExternalName"):
|
||||
return &applyconfigurationservicev0alpha1.ExternalNameApplyConfiguration{}
|
||||
case servicev0alpha1.SchemeGroupVersion.WithKind("ExternalNameSpec"):
|
||||
return &applyconfigurationservicev0alpha1.ExternalNameSpecApplyConfiguration{}
|
||||
|
||||
}
|
||||
return nil
|
||||
|
@ -8,6 +8,7 @@ import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
|
||||
notificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1"
|
||||
servicev0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/service/v0alpha1"
|
||||
discovery "k8s.io/client-go/discovery"
|
||||
rest "k8s.io/client-go/rest"
|
||||
@ -16,13 +17,20 @@ import (
|
||||
|
||||
type Interface interface {
|
||||
Discovery() discovery.DiscoveryInterface
|
||||
NotificationsV0alpha1() notificationsv0alpha1.NotificationsV0alpha1Interface
|
||||
ServiceV0alpha1() servicev0alpha1.ServiceV0alpha1Interface
|
||||
}
|
||||
|
||||
// Clientset contains the clients for groups.
|
||||
type Clientset struct {
|
||||
*discovery.DiscoveryClient
|
||||
serviceV0alpha1 *servicev0alpha1.ServiceV0alpha1Client
|
||||
notificationsV0alpha1 *notificationsv0alpha1.NotificationsV0alpha1Client
|
||||
serviceV0alpha1 *servicev0alpha1.ServiceV0alpha1Client
|
||||
}
|
||||
|
||||
// NotificationsV0alpha1 retrieves the NotificationsV0alpha1Client
|
||||
func (c *Clientset) NotificationsV0alpha1() notificationsv0alpha1.NotificationsV0alpha1Interface {
|
||||
return c.notificationsV0alpha1
|
||||
}
|
||||
|
||||
// ServiceV0alpha1 retrieves the ServiceV0alpha1Client
|
||||
@ -74,6 +82,10 @@ func NewForConfigAndClient(c *rest.Config, httpClient *http.Client) (*Clientset,
|
||||
|
||||
var cs Clientset
|
||||
var err error
|
||||
cs.notificationsV0alpha1, err = notificationsv0alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cs.serviceV0alpha1, err = servicev0alpha1.NewForConfigAndClient(&configShallowCopy, httpClient)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -99,6 +111,7 @@ func NewForConfigOrDie(c *rest.Config) *Clientset {
|
||||
// New creates a new Clientset for the given RESTClient.
|
||||
func New(c rest.Interface) *Clientset {
|
||||
var cs Clientset
|
||||
cs.notificationsV0alpha1 = notificationsv0alpha1.New(c)
|
||||
cs.serviceV0alpha1 = servicev0alpha1.New(c)
|
||||
|
||||
cs.DiscoveryClient = discovery.NewDiscoveryClient(c)
|
||||
|
@ -6,6 +6,8 @@ package fake
|
||||
|
||||
import (
|
||||
clientset "github.com/grafana/grafana/pkg/generated/clientset/versioned"
|
||||
notificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1"
|
||||
fakenotificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake"
|
||||
servicev0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/service/v0alpha1"
|
||||
fakeservicev0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/service/v0alpha1/fake"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -65,6 +67,11 @@ var (
|
||||
_ testing.FakeClient = &Clientset{}
|
||||
)
|
||||
|
||||
// NotificationsV0alpha1 retrieves the NotificationsV0alpha1Client
|
||||
func (c *Clientset) NotificationsV0alpha1() notificationsv0alpha1.NotificationsV0alpha1Interface {
|
||||
return &fakenotificationsv0alpha1.FakeNotificationsV0alpha1{Fake: &c.Fake}
|
||||
}
|
||||
|
||||
// ServiceV0alpha1 retrieves the ServiceV0alpha1Client
|
||||
func (c *Clientset) ServiceV0alpha1() servicev0alpha1.ServiceV0alpha1Interface {
|
||||
return &fakeservicev0alpha1.FakeServiceV0alpha1{Fake: &c.Fake}
|
||||
|
@ -5,6 +5,7 @@
|
||||
package fake
|
||||
|
||||
import (
|
||||
notificationsv0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
@ -17,6 +18,7 @@ var scheme = runtime.NewScheme()
|
||||
var codecs = serializer.NewCodecFactory(scheme)
|
||||
|
||||
var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
notificationsv0alpha1.AddToScheme,
|
||||
servicev0alpha1.AddToScheme,
|
||||
}
|
||||
|
||||
|
@ -5,6 +5,7 @@
|
||||
package scheme
|
||||
|
||||
import (
|
||||
notificationsv0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
@ -17,6 +18,7 @@ var Scheme = runtime.NewScheme()
|
||||
var Codecs = serializer.NewCodecFactory(Scheme)
|
||||
var ParameterCodec = runtime.NewParameterCodec(Scheme)
|
||||
var localSchemeBuilder = runtime.SchemeBuilder{
|
||||
notificationsv0alpha1.AddToScheme,
|
||||
servicev0alpha1.AddToScheme,
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,93 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/generated/clientset/versioned/scheme"
|
||||
rest "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type NotificationsV0alpha1Interface interface {
|
||||
RESTClient() rest.Interface
|
||||
TimeIntervalsGetter
|
||||
}
|
||||
|
||||
// NotificationsV0alpha1Client is used to interact with features provided by the notifications.alerting.grafana.app group.
|
||||
type NotificationsV0alpha1Client struct {
|
||||
restClient rest.Interface
|
||||
}
|
||||
|
||||
func (c *NotificationsV0alpha1Client) TimeIntervals(namespace string) TimeIntervalInterface {
|
||||
return newTimeIntervals(c, namespace)
|
||||
}
|
||||
|
||||
// NewForConfig creates a new NotificationsV0alpha1Client for the given config.
|
||||
// NewForConfig is equivalent to NewForConfigAndClient(c, httpClient),
|
||||
// where httpClient was generated with rest.HTTPClientFor(c).
|
||||
func NewForConfig(c *rest.Config) (*NotificationsV0alpha1Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
httpClient, err := rest.HTTPClientFor(&config)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return NewForConfigAndClient(&config, httpClient)
|
||||
}
|
||||
|
||||
// NewForConfigAndClient creates a new NotificationsV0alpha1Client for the given config and http client.
|
||||
// Note the http client provided takes precedence over the configured transport values.
|
||||
func NewForConfigAndClient(c *rest.Config, h *http.Client) (*NotificationsV0alpha1Client, error) {
|
||||
config := *c
|
||||
if err := setConfigDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := rest.RESTClientForConfigAndClient(&config, h)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &NotificationsV0alpha1Client{client}, nil
|
||||
}
|
||||
|
||||
// NewForConfigOrDie creates a new NotificationsV0alpha1Client for the given config and
|
||||
// panics if there is an error in the config.
|
||||
func NewForConfigOrDie(c *rest.Config) *NotificationsV0alpha1Client {
|
||||
client, err := NewForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
}
|
||||
|
||||
// New creates a new NotificationsV0alpha1Client for the given RESTClient.
|
||||
func New(c rest.Interface) *NotificationsV0alpha1Client {
|
||||
return &NotificationsV0alpha1Client{c}
|
||||
}
|
||||
|
||||
func setConfigDefaults(config *rest.Config) error {
|
||||
gv := v0alpha1.SchemeGroupVersion
|
||||
config.GroupVersion = &gv
|
||||
config.APIPath = "/apis"
|
||||
config.NegotiatedSerializer = scheme.Codecs.WithoutConversion()
|
||||
|
||||
if config.UserAgent == "" {
|
||||
config.UserAgent = rest.DefaultKubernetesUserAgent()
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *NotificationsV0alpha1Client) RESTClient() rest.Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.restClient
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// This package has the automatically generated typed clients.
|
||||
package v0alpha1
|
@ -0,0 +1,6 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
// Package fake has the automatically generated clients.
|
||||
package fake
|
@ -0,0 +1,26 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package fake
|
||||
|
||||
import (
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1"
|
||||
rest "k8s.io/client-go/rest"
|
||||
testing "k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
type FakeNotificationsV0alpha1 struct {
|
||||
*testing.Fake
|
||||
}
|
||||
|
||||
func (c *FakeNotificationsV0alpha1) TimeIntervals(namespace string) v0alpha1.TimeIntervalInterface {
|
||||
return &FakeTimeIntervals{c, namespace}
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *FakeNotificationsV0alpha1) RESTClient() rest.Interface {
|
||||
var ret *rest.RESTClient
|
||||
return ret
|
||||
}
|
@ -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"
|
||||
)
|
||||
|
||||
// FakeTimeIntervals implements TimeIntervalInterface
|
||||
type FakeTimeIntervals struct {
|
||||
Fake *FakeNotificationsV0alpha1
|
||||
ns string
|
||||
}
|
||||
|
||||
var timeintervalsResource = v0alpha1.SchemeGroupVersion.WithResource("timeintervals")
|
||||
|
||||
var timeintervalsKind = v0alpha1.SchemeGroupVersion.WithKind("TimeInterval")
|
||||
|
||||
// Get takes name of the timeInterval, and returns the corresponding timeInterval object, and an error if there is any.
|
||||
func (c *FakeTimeIntervals) Get(ctx context.Context, name string, options v1.GetOptions) (result *v0alpha1.TimeInterval, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewGetAction(timeintervalsResource, c.ns, name), &v0alpha1.TimeInterval{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v0alpha1.TimeInterval), err
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of TimeIntervals that match those selectors.
|
||||
func (c *FakeTimeIntervals) List(ctx context.Context, opts v1.ListOptions) (result *v0alpha1.TimeIntervalList, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewListAction(timeintervalsResource, timeintervalsKind, c.ns, opts), &v0alpha1.TimeIntervalList{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
label, _, _ := testing.ExtractFromListOptions(opts)
|
||||
if label == nil {
|
||||
label = labels.Everything()
|
||||
}
|
||||
list := &v0alpha1.TimeIntervalList{ListMeta: obj.(*v0alpha1.TimeIntervalList).ListMeta}
|
||||
for _, item := range obj.(*v0alpha1.TimeIntervalList).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 timeIntervals.
|
||||
func (c *FakeTimeIntervals) Watch(ctx context.Context, opts v1.ListOptions) (watch.Interface, error) {
|
||||
return c.Fake.
|
||||
InvokesWatch(testing.NewWatchAction(timeintervalsResource, c.ns, opts))
|
||||
|
||||
}
|
||||
|
||||
// Create takes the representation of a timeInterval and creates it. Returns the server's representation of the timeInterval, and an error, if there is any.
|
||||
func (c *FakeTimeIntervals) Create(ctx context.Context, timeInterval *v0alpha1.TimeInterval, opts v1.CreateOptions) (result *v0alpha1.TimeInterval, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewCreateAction(timeintervalsResource, c.ns, timeInterval), &v0alpha1.TimeInterval{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v0alpha1.TimeInterval), err
|
||||
}
|
||||
|
||||
// Update takes the representation of a timeInterval and updates it. Returns the server's representation of the timeInterval, and an error, if there is any.
|
||||
func (c *FakeTimeIntervals) Update(ctx context.Context, timeInterval *v0alpha1.TimeInterval, opts v1.UpdateOptions) (result *v0alpha1.TimeInterval, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewUpdateAction(timeintervalsResource, c.ns, timeInterval), &v0alpha1.TimeInterval{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v0alpha1.TimeInterval), err
|
||||
}
|
||||
|
||||
// Delete takes name of the timeInterval and deletes it. Returns an error if one occurs.
|
||||
func (c *FakeTimeIntervals) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
_, err := c.Fake.
|
||||
Invokes(testing.NewDeleteActionWithOptions(timeintervalsResource, c.ns, name, opts), &v0alpha1.TimeInterval{})
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *FakeTimeIntervals) DeleteCollection(ctx context.Context, opts v1.DeleteOptions, listOpts v1.ListOptions) error {
|
||||
action := testing.NewDeleteCollectionAction(timeintervalsResource, c.ns, listOpts)
|
||||
|
||||
_, err := c.Fake.Invokes(action, &v0alpha1.TimeIntervalList{})
|
||||
return err
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched timeInterval.
|
||||
func (c *FakeTimeIntervals) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v0alpha1.TimeInterval, err error) {
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(timeintervalsResource, c.ns, name, pt, data, subresources...), &v0alpha1.TimeInterval{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v0alpha1.TimeInterval), err
|
||||
}
|
||||
|
||||
// Apply takes the given apply declarative configuration, applies it and returns the applied timeInterval.
|
||||
func (c *FakeTimeIntervals) Apply(ctx context.Context, timeInterval *alertingnotificationsv0alpha1.TimeIntervalApplyConfiguration, opts v1.ApplyOptions) (result *v0alpha1.TimeInterval, err error) {
|
||||
if timeInterval == nil {
|
||||
return nil, fmt.Errorf("timeInterval provided to Apply must not be nil")
|
||||
}
|
||||
data, err := json.Marshal(timeInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := timeInterval.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("timeInterval.Name must be provided to Apply")
|
||||
}
|
||||
obj, err := c.Fake.
|
||||
Invokes(testing.NewPatchSubresourceAction(timeintervalsResource, c.ns, *name, types.ApplyPatchType, data), &v0alpha1.TimeInterval{})
|
||||
|
||||
if obj == nil {
|
||||
return nil, err
|
||||
}
|
||||
return obj.(*v0alpha1.TimeInterval), err
|
||||
}
|
@ -0,0 +1,7 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by client-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
type TimeIntervalExpansion interface{}
|
@ -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"
|
||||
)
|
||||
|
||||
// TimeIntervalsGetter has a method to return a TimeIntervalInterface.
|
||||
// A group's client should implement this interface.
|
||||
type TimeIntervalsGetter interface {
|
||||
TimeIntervals(namespace string) TimeIntervalInterface
|
||||
}
|
||||
|
||||
// TimeIntervalInterface has methods to work with TimeInterval resources.
|
||||
type TimeIntervalInterface interface {
|
||||
Create(ctx context.Context, timeInterval *v0alpha1.TimeInterval, opts v1.CreateOptions) (*v0alpha1.TimeInterval, error)
|
||||
Update(ctx context.Context, timeInterval *v0alpha1.TimeInterval, opts v1.UpdateOptions) (*v0alpha1.TimeInterval, 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.TimeInterval, error)
|
||||
List(ctx context.Context, opts v1.ListOptions) (*v0alpha1.TimeIntervalList, 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.TimeInterval, err error)
|
||||
Apply(ctx context.Context, timeInterval *alertingnotificationsv0alpha1.TimeIntervalApplyConfiguration, opts v1.ApplyOptions) (result *v0alpha1.TimeInterval, err error)
|
||||
TimeIntervalExpansion
|
||||
}
|
||||
|
||||
// timeIntervals implements TimeIntervalInterface
|
||||
type timeIntervals struct {
|
||||
client rest.Interface
|
||||
ns string
|
||||
}
|
||||
|
||||
// newTimeIntervals returns a TimeIntervals
|
||||
func newTimeIntervals(c *NotificationsV0alpha1Client, namespace string) *timeIntervals {
|
||||
return &timeIntervals{
|
||||
client: c.RESTClient(),
|
||||
ns: namespace,
|
||||
}
|
||||
}
|
||||
|
||||
// Get takes name of the timeInterval, and returns the corresponding timeInterval object, and an error if there is any.
|
||||
func (c *timeIntervals) Get(ctx context.Context, name string, options v1.GetOptions) (result *v0alpha1.TimeInterval, err error) {
|
||||
result = &v0alpha1.TimeInterval{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("timeintervals").
|
||||
Name(name).
|
||||
VersionedParams(&options, scheme.ParameterCodec).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// List takes label and field selectors, and returns the list of TimeIntervals that match those selectors.
|
||||
func (c *timeIntervals) List(ctx context.Context, opts v1.ListOptions) (result *v0alpha1.TimeIntervalList, err error) {
|
||||
var timeout time.Duration
|
||||
if opts.TimeoutSeconds != nil {
|
||||
timeout = time.Duration(*opts.TimeoutSeconds) * time.Second
|
||||
}
|
||||
result = &v0alpha1.TimeIntervalList{}
|
||||
err = c.client.Get().
|
||||
Namespace(c.ns).
|
||||
Resource("timeintervals").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Watch returns a watch.Interface that watches the requested timeIntervals.
|
||||
func (c *timeIntervals) 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("timeintervals").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Watch(ctx)
|
||||
}
|
||||
|
||||
// Create takes the representation of a timeInterval and creates it. Returns the server's representation of the timeInterval, and an error, if there is any.
|
||||
func (c *timeIntervals) Create(ctx context.Context, timeInterval *v0alpha1.TimeInterval, opts v1.CreateOptions) (result *v0alpha1.TimeInterval, err error) {
|
||||
result = &v0alpha1.TimeInterval{}
|
||||
err = c.client.Post().
|
||||
Namespace(c.ns).
|
||||
Resource("timeintervals").
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(timeInterval).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Update takes the representation of a timeInterval and updates it. Returns the server's representation of the timeInterval, and an error, if there is any.
|
||||
func (c *timeIntervals) Update(ctx context.Context, timeInterval *v0alpha1.TimeInterval, opts v1.UpdateOptions) (result *v0alpha1.TimeInterval, err error) {
|
||||
result = &v0alpha1.TimeInterval{}
|
||||
err = c.client.Put().
|
||||
Namespace(c.ns).
|
||||
Resource("timeintervals").
|
||||
Name(timeInterval.Name).
|
||||
VersionedParams(&opts, scheme.ParameterCodec).
|
||||
Body(timeInterval).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
||||
|
||||
// Delete takes name of the timeInterval and deletes it. Returns an error if one occurs.
|
||||
func (c *timeIntervals) Delete(ctx context.Context, name string, opts v1.DeleteOptions) error {
|
||||
return c.client.Delete().
|
||||
Namespace(c.ns).
|
||||
Resource("timeintervals").
|
||||
Name(name).
|
||||
Body(&opts).
|
||||
Do(ctx).
|
||||
Error()
|
||||
}
|
||||
|
||||
// DeleteCollection deletes a collection of objects.
|
||||
func (c *timeIntervals) 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("timeintervals").
|
||||
VersionedParams(&listOpts, scheme.ParameterCodec).
|
||||
Timeout(timeout).
|
||||
Body(&opts).
|
||||
Do(ctx).
|
||||
Error()
|
||||
}
|
||||
|
||||
// Patch applies the patch and returns the patched timeInterval.
|
||||
func (c *timeIntervals) Patch(ctx context.Context, name string, pt types.PatchType, data []byte, opts v1.PatchOptions, subresources ...string) (result *v0alpha1.TimeInterval, err error) {
|
||||
result = &v0alpha1.TimeInterval{}
|
||||
err = c.client.Patch(pt).
|
||||
Namespace(c.ns).
|
||||
Resource("timeintervals").
|
||||
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 timeInterval.
|
||||
func (c *timeIntervals) Apply(ctx context.Context, timeInterval *alertingnotificationsv0alpha1.TimeIntervalApplyConfiguration, opts v1.ApplyOptions) (result *v0alpha1.TimeInterval, err error) {
|
||||
if timeInterval == nil {
|
||||
return nil, fmt.Errorf("timeInterval provided to Apply must not be nil")
|
||||
}
|
||||
patchOpts := opts.ToPatchOptions()
|
||||
data, err := json.Marshal(timeInterval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
name := timeInterval.Name
|
||||
if name == nil {
|
||||
return nil, fmt.Errorf("timeInterval.Name must be provided to Apply")
|
||||
}
|
||||
result = &v0alpha1.TimeInterval{}
|
||||
err = c.client.Patch(types.ApplyPatchType).
|
||||
Namespace(c.ns).
|
||||
Resource("timeintervals").
|
||||
Name(*name).
|
||||
VersionedParams(&patchOpts, scheme.ParameterCodec).
|
||||
Body(data).
|
||||
Do(ctx).
|
||||
Into(result)
|
||||
return
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package alerting_notifications
|
||||
|
||||
import (
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1"
|
||||
internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces"
|
||||
)
|
||||
|
||||
// Interface provides access to each of this group's versions.
|
||||
type Interface interface {
|
||||
// V0alpha1 provides access to shared informers for resources in V0alpha1.
|
||||
V0alpha1() v0alpha1.Interface
|
||||
}
|
||||
|
||||
type group struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||
return &group{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// V0alpha1 returns a new v0alpha1.Interface.
|
||||
func (g *group) V0alpha1() v0alpha1.Interface {
|
||||
return v0alpha1.New(g.factory, g.namespace, g.tweakListOptions)
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by informer-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces"
|
||||
)
|
||||
|
||||
// Interface provides access to all the informers in this group version.
|
||||
type Interface interface {
|
||||
// TimeIntervals returns a TimeIntervalInformer.
|
||||
TimeIntervals() TimeIntervalInformer
|
||||
}
|
||||
|
||||
type version struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
namespace string
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
}
|
||||
|
||||
// New returns a new Interface.
|
||||
func New(f internalinterfaces.SharedInformerFactory, namespace string, tweakListOptions internalinterfaces.TweakListOptionsFunc) Interface {
|
||||
return &version{factory: f, namespace: namespace, tweakListOptions: tweakListOptions}
|
||||
}
|
||||
|
||||
// TimeIntervals returns a TimeIntervalInformer.
|
||||
func (v *version) TimeIntervals() TimeIntervalInformer {
|
||||
return &timeIntervalInformer{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"
|
||||
)
|
||||
|
||||
// TimeIntervalInformer provides access to a shared informer and lister for
|
||||
// TimeIntervals.
|
||||
type TimeIntervalInformer interface {
|
||||
Informer() cache.SharedIndexInformer
|
||||
Lister() v0alpha1.TimeIntervalLister
|
||||
}
|
||||
|
||||
type timeIntervalInformer struct {
|
||||
factory internalinterfaces.SharedInformerFactory
|
||||
tweakListOptions internalinterfaces.TweakListOptionsFunc
|
||||
namespace string
|
||||
}
|
||||
|
||||
// NewTimeIntervalInformer constructs a new informer for TimeInterval 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 NewTimeIntervalInformer(client versioned.Interface, namespace string, resyncPeriod time.Duration, indexers cache.Indexers) cache.SharedIndexInformer {
|
||||
return NewFilteredTimeIntervalInformer(client, namespace, resyncPeriod, indexers, nil)
|
||||
}
|
||||
|
||||
// NewFilteredTimeIntervalInformer constructs a new informer for TimeInterval 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 NewFilteredTimeIntervalInformer(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().TimeIntervals(namespace).List(context.TODO(), options)
|
||||
},
|
||||
WatchFunc: func(options v1.ListOptions) (watch.Interface, error) {
|
||||
if tweakListOptions != nil {
|
||||
tweakListOptions(&options)
|
||||
}
|
||||
return client.NotificationsV0alpha1().TimeIntervals(namespace).Watch(context.TODO(), options)
|
||||
},
|
||||
},
|
||||
&alertingnotificationsv0alpha1.TimeInterval{},
|
||||
resyncPeriod,
|
||||
indexers,
|
||||
)
|
||||
}
|
||||
|
||||
func (f *timeIntervalInformer) defaultInformer(client versioned.Interface, resyncPeriod time.Duration) cache.SharedIndexInformer {
|
||||
return NewFilteredTimeIntervalInformer(client, f.namespace, resyncPeriod, cache.Indexers{cache.NamespaceIndex: cache.MetaNamespaceIndexFunc}, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *timeIntervalInformer) Informer() cache.SharedIndexInformer {
|
||||
return f.factory.InformerFor(&alertingnotificationsv0alpha1.TimeInterval{}, f.defaultInformer)
|
||||
}
|
||||
|
||||
func (f *timeIntervalInformer) Lister() v0alpha1.TimeIntervalLister {
|
||||
return v0alpha1.NewTimeIntervalLister(f.Informer().GetIndexer())
|
||||
}
|
@ -10,6 +10,7 @@ import (
|
||||
time "time"
|
||||
|
||||
versioned "github.com/grafana/grafana/pkg/generated/clientset/versioned"
|
||||
alertingnotifications "github.com/grafana/grafana/pkg/generated/informers/externalversions/alerting_notifications"
|
||||
internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces"
|
||||
service "github.com/grafana/grafana/pkg/generated/informers/externalversions/service"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
@ -239,9 +240,14 @@ type SharedInformerFactory interface {
|
||||
// client.
|
||||
InformerFor(obj runtime.Object, newFunc internalinterfaces.NewInformerFunc) cache.SharedIndexInformer
|
||||
|
||||
Notifications() alertingnotifications.Interface
|
||||
Service() service.Interface
|
||||
}
|
||||
|
||||
func (f *sharedInformerFactory) Notifications() alertingnotifications.Interface {
|
||||
return alertingnotifications.New(f, f.namespace, f.tweakListOptions)
|
||||
}
|
||||
|
||||
func (f *sharedInformerFactory) Service() service.Interface {
|
||||
return service.New(f, f.namespace, f.tweakListOptions)
|
||||
}
|
||||
|
@ -7,7 +7,8 @@ package externalversions
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
|
||||
v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
servicev0alpha1 "github.com/grafana/grafana/pkg/apis/service/v0alpha1"
|
||||
schema "k8s.io/apimachinery/pkg/runtime/schema"
|
||||
cache "k8s.io/client-go/tools/cache"
|
||||
)
|
||||
@ -38,8 +39,12 @@ func (f *genericInformer) Lister() cache.GenericLister {
|
||||
// TODO extend this to unknown resources with a client pool
|
||||
func (f *sharedInformerFactory) ForResource(resource schema.GroupVersionResource) (GenericInformer, error) {
|
||||
switch resource {
|
||||
// Group=service.grafana.app, Version=v0alpha1
|
||||
case v0alpha1.SchemeGroupVersion.WithResource("externalnames"):
|
||||
// Group=notifications.alerting.grafana.app, Version=v0alpha1
|
||||
case v0alpha1.SchemeGroupVersion.WithResource("timeintervals"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Notifications().V0alpha1().TimeIntervals().Informer()}, nil
|
||||
|
||||
// Group=service.grafana.app, Version=v0alpha1
|
||||
case servicev0alpha1.SchemeGroupVersion.WithResource("externalnames"):
|
||||
return &genericInformer{resource: resource.GroupResource(), informer: f.Service().V0alpha1().ExternalNames().Informer()}, nil
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,13 @@
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
// Code generated by lister-gen. DO NOT EDIT.
|
||||
|
||||
package v0alpha1
|
||||
|
||||
// TimeIntervalListerExpansion allows custom methods to be added to
|
||||
// TimeIntervalLister.
|
||||
type TimeIntervalListerExpansion interface{}
|
||||
|
||||
// TimeIntervalNamespaceListerExpansion allows custom methods to be added to
|
||||
// TimeIntervalNamespaceLister.
|
||||
type TimeIntervalNamespaceListerExpansion interface{}
|
@ -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"
|
||||
)
|
||||
|
||||
// TimeIntervalLister helps list TimeIntervals.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type TimeIntervalLister interface {
|
||||
// List lists all TimeIntervals in the indexer.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []*v0alpha1.TimeInterval, err error)
|
||||
// TimeIntervals returns an object that can list and get TimeIntervals.
|
||||
TimeIntervals(namespace string) TimeIntervalNamespaceLister
|
||||
TimeIntervalListerExpansion
|
||||
}
|
||||
|
||||
// timeIntervalLister implements the TimeIntervalLister interface.
|
||||
type timeIntervalLister struct {
|
||||
indexer cache.Indexer
|
||||
}
|
||||
|
||||
// NewTimeIntervalLister returns a new TimeIntervalLister.
|
||||
func NewTimeIntervalLister(indexer cache.Indexer) TimeIntervalLister {
|
||||
return &timeIntervalLister{indexer: indexer}
|
||||
}
|
||||
|
||||
// List lists all TimeIntervals in the indexer.
|
||||
func (s *timeIntervalLister) List(selector labels.Selector) (ret []*v0alpha1.TimeInterval, err error) {
|
||||
err = cache.ListAll(s.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v0alpha1.TimeInterval))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// TimeIntervals returns an object that can list and get TimeIntervals.
|
||||
func (s *timeIntervalLister) TimeIntervals(namespace string) TimeIntervalNamespaceLister {
|
||||
return timeIntervalNamespaceLister{indexer: s.indexer, namespace: namespace}
|
||||
}
|
||||
|
||||
// TimeIntervalNamespaceLister helps list and get TimeIntervals.
|
||||
// All objects returned here must be treated as read-only.
|
||||
type TimeIntervalNamespaceLister interface {
|
||||
// List lists all TimeIntervals in the indexer for a given namespace.
|
||||
// Objects returned here must be treated as read-only.
|
||||
List(selector labels.Selector) (ret []*v0alpha1.TimeInterval, err error)
|
||||
// Get retrieves the TimeInterval from the indexer for a given namespace and name.
|
||||
// Objects returned here must be treated as read-only.
|
||||
Get(name string) (*v0alpha1.TimeInterval, error)
|
||||
TimeIntervalNamespaceListerExpansion
|
||||
}
|
||||
|
||||
// timeIntervalNamespaceLister implements the TimeIntervalNamespaceLister
|
||||
// interface.
|
||||
type timeIntervalNamespaceLister struct {
|
||||
indexer cache.Indexer
|
||||
namespace string
|
||||
}
|
||||
|
||||
// List lists all TimeIntervals in the indexer for a given namespace.
|
||||
func (s timeIntervalNamespaceLister) List(selector labels.Selector) (ret []*v0alpha1.TimeInterval, err error) {
|
||||
err = cache.ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(*v0alpha1.TimeInterval))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
// Get retrieves the TimeInterval from the indexer for a given namespace and name.
|
||||
func (s timeIntervalNamespaceLister) Get(name string) (*v0alpha1.TimeInterval, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(v0alpha1.Resource("timeinterval"), name)
|
||||
}
|
||||
return obj.(*v0alpha1.TimeInterval), nil
|
||||
}
|
112
pkg/registry/apis/alerting/notifications/register.go
Normal file
112
pkg/registry/apis/alerting/notifications/register.go
Normal file
@ -0,0 +1,112 @@
|
||||
package notifications
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
|
||||
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"
|
||||
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"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert"
|
||||
"github.com/grafana/grafana/pkg/setting"
|
||||
)
|
||||
|
||||
var _ builder.APIGroupBuilder = (*NotificationsAPIBuilder)(nil)
|
||||
|
||||
// This is used just so wire has something unique to return
|
||||
type NotificationsAPIBuilder struct {
|
||||
authz accesscontrol.AccessControl
|
||||
ng *ngalert.AlertNG
|
||||
namespacer request.NamespaceMapper
|
||||
gv schema.GroupVersion
|
||||
}
|
||||
|
||||
func (t NotificationsAPIBuilder) GetDesiredDualWriterMode(dualWrite bool, toMode map[string]grafanarest.DualWriterMode) grafanarest.DualWriterMode {
|
||||
// Add required configuration support in order to enable other modes. For an example, see pkg/registry/apis/playlist/register.go
|
||||
return grafanarest.Mode0
|
||||
}
|
||||
|
||||
func RegisterAPIService(
|
||||
features featuremgmt.FeatureToggles,
|
||||
apiregistration builder.APIRegistrar,
|
||||
cfg *setting.Cfg,
|
||||
ng *ngalert.AlertNG,
|
||||
) *NotificationsAPIBuilder {
|
||||
if ng.IsDisabled() || !features.IsEnabledGlobally(featuremgmt.FlagAlertingApiServer) {
|
||||
return nil
|
||||
}
|
||||
builder := &NotificationsAPIBuilder{
|
||||
ng: ng,
|
||||
namespacer: request.GetNamespaceMapper(cfg),
|
||||
gv: notificationsModels.SchemeGroupVersion,
|
||||
authz: ng.Api.AccessControl,
|
||||
}
|
||||
apiregistration.RegisterAPI(builder)
|
||||
return builder
|
||||
}
|
||||
|
||||
func (t NotificationsAPIBuilder) GetGroupVersion() schema.GroupVersion {
|
||||
return t.gv
|
||||
}
|
||||
|
||||
func (t NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
||||
err := notificationsModels.AddToScheme(scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return scheme.SetVersionPriority(notificationsModels.SchemeGroupVersion)
|
||||
}
|
||||
|
||||
func (t NotificationsAPIBuilder) GetAPIGroupInfo(
|
||||
scheme *runtime.Scheme,
|
||||
codecs serializer.CodecFactory,
|
||||
optsGetter generic.RESTOptionsGetter,
|
||||
desiredMode grafanarest.DualWriterMode,
|
||||
reg prometheus.Registerer,
|
||||
) (*genericapiserver.APIGroupInfo, error) {
|
||||
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(notificationsModels.GROUP, scheme, metav1.ParameterCodec, codecs)
|
||||
|
||||
intervals, err := timeInterval.NewStorage(t.ng.Api.MuteTimings, t.namespacer, scheme, desiredMode, optsGetter, reg)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize time-interval storage: %w", err)
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[notificationsModels.VERSION] = map[string]rest.Storage{
|
||||
notificationsModels.TimeIntervalResourceInfo.StoragePath(): intervals,
|
||||
}
|
||||
return &apiGroupInfo, nil
|
||||
}
|
||||
|
||||
func (t NotificationsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
||||
return notificationsModels.GetOpenAPIDefinitions
|
||||
}
|
||||
|
||||
func (t NotificationsAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t NotificationsAPIBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||
return authorizer.AuthorizerFunc(
|
||||
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
|
||||
switch a.GetResource() {
|
||||
case notificationsModels.TimeIntervalResourceInfo.GroupResource().Resource:
|
||||
return timeInterval.Authorize(ctx, t.authz, a)
|
||||
}
|
||||
return authorizer.DecisionNoOpinion, "", nil
|
||||
})
|
||||
}
|
@ -0,0 +1,54 @@
|
||||
package timeinterval
|
||||
|
||||
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
|
||||
case "update":
|
||||
action = accesscontrol.EvalAny(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsTimeIntervalsWrite),
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsWrite),
|
||||
)
|
||||
case "deletecollection":
|
||||
fallthrough
|
||||
case "delete":
|
||||
action = accesscontrol.EvalAny(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsTimeIntervalsDelete),
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsWrite),
|
||||
)
|
||||
}
|
||||
|
||||
eval := accesscontrol.EvalAny(
|
||||
accesscontrol.EvalPermission(accesscontrol.ActionAlertingNotificationsTimeIntervalsRead),
|
||||
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
|
||||
}
|
@ -0,0 +1,97 @@
|
||||
package timeinterval
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
func getIntervalUID(t definitions.MuteTimeInterval) string {
|
||||
sum := fnv.New64()
|
||||
_, _ = sum.Write([]byte(t.Name))
|
||||
return fmt.Sprintf("%016x", sum.Sum64())
|
||||
}
|
||||
|
||||
func convertToK8sResources(orgID int64, intervals []definitions.MuteTimeInterval, namespacer request.NamespaceMapper) (*model.TimeIntervalList, error) {
|
||||
data, err := json.Marshal(intervals)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var specs []model.TimeIntervalSpec
|
||||
err = json.Unmarshal(data, &specs)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &model.TimeIntervalList{}
|
||||
for idx := range specs {
|
||||
interval := intervals[idx]
|
||||
spec := specs[idx]
|
||||
uid := getIntervalUID(interval) // TODO replace to stable UID when we switch to normal storage
|
||||
result.Items = append(result.Items, model.TimeInterval{
|
||||
TypeMeta: resourceInfo.TypeMeta(),
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: types.UID(uid), // TODO 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(interval.Provenance),
|
||||
},
|
||||
ResourceVersion: interval.Version,
|
||||
},
|
||||
Spec: spec,
|
||||
})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func convertToK8sResource(orgID int64, interval definitions.MuteTimeInterval, namespacer request.NamespaceMapper) (*model.TimeInterval, error) {
|
||||
data, err := json.Marshal(interval)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
spec := model.TimeIntervalSpec{}
|
||||
err = json.Unmarshal(data, &spec)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
uid := getIntervalUID(interval) // TODO replace to stable UID when we switch to normal storage
|
||||
return &model.TimeInterval{
|
||||
TypeMeta: resourceInfo.TypeMeta(),
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
UID: types.UID(uid), // TODO 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(interval.Provenance),
|
||||
},
|
||||
ResourceVersion: interval.Version,
|
||||
},
|
||||
Spec: spec,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func convertToDomainModel(interval *model.TimeInterval) (definitions.MuteTimeInterval, error) {
|
||||
b, err := json.Marshal(interval.Spec)
|
||||
if err != nil {
|
||||
return definitions.MuteTimeInterval{}, err
|
||||
}
|
||||
result := definitions.MuteTimeInterval{}
|
||||
err = json.Unmarshal(b, &result)
|
||||
if err != nil {
|
||||
return definitions.MuteTimeInterval{}, err
|
||||
}
|
||||
result.Version = interval.ResourceVersion
|
||||
err = result.Validate()
|
||||
if err != nil {
|
||||
return definitions.MuteTimeInterval{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
@ -0,0 +1,205 @@
|
||||
package timeinterval
|
||||
|
||||
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"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
)
|
||||
|
||||
var (
|
||||
_ grafanaRest.LegacyStorage = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
var resourceInfo = notifications.TimeIntervalResourceInfo
|
||||
|
||||
type TimeIntervalService interface {
|
||||
GetMuteTimings(ctx context.Context, orgID int64) ([]definitions.MuteTimeInterval, error)
|
||||
GetMuteTiming(ctx context.Context, name string, orgID int64) (definitions.MuteTimeInterval, error)
|
||||
CreateMuteTiming(ctx context.Context, mt definitions.MuteTimeInterval, orgID int64) (definitions.MuteTimeInterval, error)
|
||||
UpdateMuteTiming(ctx context.Context, mt definitions.MuteTimeInterval, orgID int64) (definitions.MuteTimeInterval, error)
|
||||
DeleteMuteTiming(ctx context.Context, name string, orgID int64, provenance definitions.Provenance, version string) error
|
||||
}
|
||||
|
||||
type legacyStorage struct {
|
||||
service TimeIntervalService
|
||||
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
|
||||
}
|
||||
|
||||
res, err := s.service.GetMuteTimings(ctx, orgId)
|
||||
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
|
||||
}
|
||||
|
||||
timings, err := s.service.GetMuteTimings(ctx, info.OrgID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, mt := range timings {
|
||||
if getIntervalUID(mt) == uid {
|
||||
return convertToK8sResource(info.OrgID, mt, s.namespacer)
|
||||
}
|
||||
}
|
||||
return nil, errors.NewNotFound(resourceInfo.GroupResource(), uid)
|
||||
}
|
||||
|
||||
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.TimeInterval)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected time-interval 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.CreateMuteTiming(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.TimeInterval)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("expected time-interval but got %s", obj.GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
interval, err := convertToDomainModel(p)
|
||||
if err != nil {
|
||||
return old, false, err
|
||||
}
|
||||
|
||||
if p.ObjectMeta.Name != getIntervalUID(interval) {
|
||||
return nil, false, errors.NewBadRequest("title of cannot be changed. Consider creating a new resource.")
|
||||
}
|
||||
|
||||
updated, err := s.service.UpdateMuteTiming(ctx, interval, 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.TimeInterval)
|
||||
if !ok {
|
||||
return nil, false, fmt.Errorf("expected time-interval but got %s", old.GetObjectKind().GroupVersionKind())
|
||||
}
|
||||
|
||||
err = s.service.DeleteMuteTiming(ctx, p.Spec.Name, 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")
|
||||
}
|
@ -0,0 +1,79 @@
|
||||
package timeinterval
|
||||
|
||||
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 TimeIntervalService,
|
||||
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: "Intervals", Type: "string", Format: "string", Description: "The display name"},
|
||||
},
|
||||
func(obj any) ([]interface{}, error) {
|
||||
r, ok := obj.(*model.TimeInterval)
|
||||
if ok {
|
||||
return []interface{}{
|
||||
r.Name,
|
||||
// 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
|
||||
}
|
@ -4,6 +4,7 @@ import (
|
||||
"context"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/dashboard"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/datasource"
|
||||
@ -35,6 +36,7 @@ func ProvideRegistryServiceSink(
|
||||
_ *peakq.PeakQAPIBuilder,
|
||||
_ *scope.ScopeAPIBuilder,
|
||||
_ *query.QueryAPIBuilder,
|
||||
_ *notifications.NotificationsAPIBuilder,
|
||||
) *Service {
|
||||
return &Service{}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package apiregistry
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
|
||||
"github.com/grafana/grafana/pkg/registry/apis/alerting/notifications"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/dashboard"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/dashboardsnapshot"
|
||||
"github.com/grafana/grafana/pkg/registry/apis/datasource"
|
||||
@ -37,4 +38,5 @@ var WireSet = wire.NewSet(
|
||||
service.RegisterAPIService,
|
||||
query.RegisterAPIService,
|
||||
scope.RegisterAPIService,
|
||||
notifications.RegisterAPIService,
|
||||
)
|
||||
|
@ -450,8 +450,9 @@ const (
|
||||
ActionAlertingNotificationsWrite = "alert.notifications:write"
|
||||
|
||||
// Alerting notifications time interval actions
|
||||
ActionAlertingNotificationsTimeIntervalsRead = "alert.notifications.time-intervals:read"
|
||||
ActionAlertingNotificationsTimeIntervalsWrite = "alert.notifications.time-intervals:write"
|
||||
ActionAlertingNotificationsTimeIntervalsRead = "alert.notifications.time-intervals:read"
|
||||
ActionAlertingNotificationsTimeIntervalsWrite = "alert.notifications.time-intervals:write"
|
||||
ActionAlertingNotificationsTimeIntervalsDelete = "alert.notifications.time-intervals:delete"
|
||||
|
||||
// Alerting receiver actions
|
||||
ActionAlertingReceiversList = "alert.notifications.receivers:list"
|
||||
|
@ -1355,6 +1355,13 @@ var (
|
||||
HideFromDocs: true,
|
||||
HideFromAdminPage: true,
|
||||
},
|
||||
{
|
||||
Name: "alertingApiServer",
|
||||
Description: "Register Alerting APIs with the K8s API server",
|
||||
Stage: FeatureStageExperimental,
|
||||
Owner: grafanaAlertingSquad,
|
||||
RequiresRestart: true,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -179,3 +179,4 @@ failWrongDSUID,experimental,@grafana/plugins-platform-backend,false,false,false
|
||||
databaseReadReplica,experimental,@grafana/grafana-backend-services-squad,false,false,false
|
||||
zanzana,experimental,@grafana/identity-access-team,false,false,false
|
||||
passScopeToDashboardApi,experimental,@grafana/dashboards-squad,false,false,false
|
||||
alertingApiServer,experimental,@grafana/alerting-squad,false,true,false
|
||||
|
|
@ -726,4 +726,8 @@ const (
|
||||
// FlagPassScopeToDashboardApi
|
||||
// Enables the passing of scopes to dashboards fetching in Grafana
|
||||
FlagPassScopeToDashboardApi = "passScopeToDashboardApi"
|
||||
|
||||
// FlagAlertingApiServer
|
||||
// Register Alerting APIs with the K8s API server
|
||||
FlagAlertingApiServer = "alertingApiServer"
|
||||
)
|
||||
|
@ -90,6 +90,19 @@
|
||||
"codeowner": "@grafana/alerting-squad"
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingApiServer",
|
||||
"resourceVersion": "1718908755156",
|
||||
"creationTimestamp": "2024-06-20T18:39:15Z"
|
||||
},
|
||||
"spec": {
|
||||
"description": "Register Alerting APIs with the K8s API server",
|
||||
"stage": "experimental",
|
||||
"codeowner": "@grafana/alerting-squad",
|
||||
"requiresRestart": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"metadata": {
|
||||
"name": "alertingBacktesting",
|
||||
@ -2349,4 +2362,4 @@
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
@ -135,7 +135,7 @@ type AlertNG struct {
|
||||
stateManager *state.Manager
|
||||
folderService folder.Service
|
||||
dashboardService dashboards.DashboardService
|
||||
api *api.API
|
||||
Api *api.API
|
||||
|
||||
// Alerting notification services
|
||||
MultiOrgAlertmanager *notifier.MultiOrgAlertmanager
|
||||
@ -377,6 +377,7 @@ func (ng *AlertNG) init() error {
|
||||
RulesPerRuleGroupLimit: ng.Cfg.UnifiedAlerting.RulesPerRuleGroupLimit,
|
||||
Tracer: ng.tracer,
|
||||
Log: log.New("ngalert.state.manager"),
|
||||
ResolvedRetention: ng.Cfg.UnifiedAlerting.ResolvedAlertRetention,
|
||||
}
|
||||
logger := log.New("ngalert.state.manager.persist")
|
||||
statePersister := state.NewSyncStatePersisiter(logger, cfg)
|
||||
@ -408,7 +409,7 @@ func (ng *AlertNG) init() error {
|
||||
ng.Cfg.UnifiedAlerting.RulesPerRuleGroupLimit, ng.Log, notifier.NewNotificationSettingsValidationService(ng.store),
|
||||
ac.NewRuleService(ng.accesscontrol))
|
||||
|
||||
ng.api = &api.API{
|
||||
ng.Api = &api.API{
|
||||
Cfg: ng.Cfg,
|
||||
DatasourceCache: ng.DataSourceCache,
|
||||
DatasourceService: ng.DataSourceService,
|
||||
@ -437,7 +438,7 @@ func (ng *AlertNG) init() error {
|
||||
Hooks: api.NewHooks(ng.Log),
|
||||
Tracer: ng.tracer,
|
||||
}
|
||||
ng.api.RegisterAPIEndpoints(ng.Metrics.GetAPIMetrics())
|
||||
ng.Api.RegisterAPIEndpoints(ng.Metrics.GetAPIMetrics())
|
||||
|
||||
if err := RegisterQuotas(ng.Cfg, ng.QuotaService, ng.store); err != nil {
|
||||
return err
|
||||
@ -515,7 +516,7 @@ func (ng *AlertNG) IsDisabled() bool {
|
||||
// GetHooks returns a facility for replacing handlers for paths. The handler hook for a path
|
||||
// is invoked after all other middleware is invoked (authentication, instrumentation).
|
||||
func (ng *AlertNG) GetHooks() *api.Hooks {
|
||||
return ng.api.Hooks
|
||||
return ng.Api.Hooks
|
||||
}
|
||||
|
||||
type Historian interface {
|
||||
|
@ -8,6 +8,10 @@ import (
|
||||
"time"
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
@ -19,9 +23,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/user"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
"go.opentelemetry.io/otel/codes"
|
||||
"go.opentelemetry.io/otel/trace"
|
||||
)
|
||||
|
||||
// Rule represents a single piece of work that is executed periodically by the ruler.
|
||||
@ -418,7 +419,7 @@ func (a *alertRule) evaluate(ctx context.Context, key ngmodels.AlertRuleKey, f f
|
||||
processDuration.Observe(a.clock.Now().Sub(start).Seconds())
|
||||
|
||||
start = a.clock.Now()
|
||||
alerts := state.FromStateTransitionToPostableAlerts(processedStates, a.stateManager, a.appURL)
|
||||
alerts := state.FromStateTransitionToPostableAlerts(e.scheduledAt, processedStates, a.stateManager, a.appURL)
|
||||
span.AddEvent("results processed", trace.WithAttributes(
|
||||
attribute.Int64("state_transitions", int64(len(processedStates))),
|
||||
attribute.Int64("alerts_to_send", int64(len(alerts.PostableAlerts))),
|
||||
|
@ -2,7 +2,7 @@ package schedule
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
context "context"
|
||||
"context"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
@ -11,19 +11,21 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
"github.com/prometheus/client_golang/prometheus/testutil"
|
||||
prometheusModel "github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/mock"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
definitions "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/infra/log/logtest"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
models "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/state"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
@ -762,8 +764,94 @@ func TestRuleRoutine(t *testing.T) {
|
||||
|
||||
require.NotEmpty(t, sch.stateManager.GetStatesForRuleUID(rule.OrgID, rule.UID))
|
||||
})
|
||||
|
||||
t.Run("when there are resolved alerts they should keep sending until retention period is over", func(t *testing.T) {
|
||||
rule := gen.With(withQueryForState(t, eval.Normal), models.RuleMuts.WithInterval(time.Second)).GenerateRef()
|
||||
|
||||
evalAppliedChan := make(chan time.Time)
|
||||
|
||||
sender := NewSyncAlertsSenderMock()
|
||||
sender.EXPECT().Send(mock.Anything, rule.GetKey(), mock.Anything).Return()
|
||||
|
||||
sch, ruleStore, _, _ := createSchedule(evalAppliedChan, sender)
|
||||
sch.stateManager.ResolvedRetention = 4 * time.Second
|
||||
sch.stateManager.ResendDelay = 2 * time.Second
|
||||
sch.stateManager.Put([]*state.State{
|
||||
stateForRule(rule, sch.clock.Now(), eval.Alerting), // Add existing Alerting state so evals will resolve.
|
||||
})
|
||||
|
||||
ruleStore.PutRule(context.Background(), rule)
|
||||
factory := ruleFactoryFromScheduler(sch)
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
t.Cleanup(cancel)
|
||||
ruleInfo := factory.new(ctx, rule)
|
||||
|
||||
go func() {
|
||||
_ = ruleInfo.Run(rule.GetKey())
|
||||
}()
|
||||
|
||||
// Evaluate 10 times:
|
||||
// 1. Send resolve #1.
|
||||
// 2. 2s resend delay.
|
||||
// 3. Send resolve #2.
|
||||
// 4. 2s resend delay.
|
||||
// 5. Send resolve #3.
|
||||
// 6. No more sends, 4s retention period is over.
|
||||
expectedResolves := map[time.Time]struct{}{
|
||||
sch.clock.Now().Add(1 * time.Second): {},
|
||||
sch.clock.Now().Add(3 * time.Second): {},
|
||||
sch.clock.Now().Add(5 * time.Second): {},
|
||||
}
|
||||
calls := 0
|
||||
for i := 1; i < 10; i++ {
|
||||
ts := sch.clock.Now().Add(time.Duration(int64(i)*rule.IntervalSeconds) * time.Second)
|
||||
ruleInfo.Eval(&Evaluation{
|
||||
scheduledAt: ts,
|
||||
rule: rule,
|
||||
})
|
||||
waitForTimeChannel(t, evalAppliedChan)
|
||||
|
||||
if _, ok := expectedResolves[ts]; ok {
|
||||
calls++
|
||||
prevCallAlerts, ok := sender.Calls()[calls-1].Arguments[2].(definitions.PostableAlerts)
|
||||
assert.Truef(t, ok, fmt.Sprintf("expected argument of function was supposed to be 'definitions.PostableAlerts' but got %T", sender.Calls()[calls-1].Arguments[2]))
|
||||
assert.Len(t, prevCallAlerts.PostableAlerts, 1)
|
||||
}
|
||||
sender.AssertNumberOfCalls(t, "Send", calls)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func ruleFactoryFromScheduler(sch *schedule) ruleFactory {
|
||||
return newRuleFactory(sch.appURL, sch.disableGrafanaFolder, sch.maxAttempts, sch.alertsSender, sch.stateManager, sch.evaluatorFactory, &sch.schedulableAlertRules, sch.clock, sch.featureToggles, sch.metrics, sch.log, sch.tracer, sch.recordingWriter, sch.evalAppliedFunc, sch.stopAppliedFunc)
|
||||
}
|
||||
|
||||
func stateForRule(rule *models.AlertRule, ts time.Time, evalState eval.State) *state.State {
|
||||
s := &state.State{
|
||||
OrgID: rule.OrgID,
|
||||
AlertRuleUID: rule.UID,
|
||||
CacheID: 0,
|
||||
State: evalState,
|
||||
Annotations: make(map[string]string),
|
||||
Labels: make(map[string]string),
|
||||
StartsAt: ts,
|
||||
EndsAt: ts,
|
||||
ResolvedAt: &ts,
|
||||
LastSentAt: &ts,
|
||||
LastEvaluationTime: ts,
|
||||
}
|
||||
for k, v := range rule.Labels {
|
||||
s.Labels[k] = v
|
||||
}
|
||||
for k, v := range state.GetRuleExtraLabels(&logtest.Fake{}, rule, "", true) {
|
||||
if _, ok := s.Labels[k]; !ok {
|
||||
s.Labels[k] = v
|
||||
}
|
||||
}
|
||||
il := models.InstanceLabels(s.Labels)
|
||||
s.Labels = data.Labels(il)
|
||||
id := il.Fingerprint()
|
||||
s.CacheID = id
|
||||
|
||||
return s
|
||||
}
|
||||
|
@ -7,15 +7,17 @@ import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
definitions "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/stretchr/testify/mock"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
mock "github.com/stretchr/testify/mock"
|
||||
)
|
||||
|
||||
// waitForTimeChannel blocks the execution until either the channel ch has some data or a timeout of 10 second expires.
|
||||
// Timeout will cause the test to fail.
|
||||
// Returns the data from the channel.
|
||||
func waitForTimeChannel(t *testing.T, ch chan time.Time) time.Time {
|
||||
t.Helper()
|
||||
select {
|
||||
case result := <-ch:
|
||||
return result
|
||||
|
@ -10,11 +10,12 @@ import (
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/go-openapi/strfmt"
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/common/model"
|
||||
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
|
||||
apimodels "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@ -73,7 +74,7 @@ func StateToPostableAlert(transition StateTransition, appURL *url.URL) *models.P
|
||||
}
|
||||
|
||||
state := alertState.State
|
||||
if alertState.Resolved {
|
||||
if alertState.ResolvedAt != nil {
|
||||
// If this is a resolved alert, we need to send an alert with the correct labels such that they will expire the previous alert.
|
||||
// In most cases the labels on the state will be correct, however when the previous alert was a NoData or Error alert, we need to
|
||||
// ensure to modify it appropriately.
|
||||
@ -139,13 +140,12 @@ func errorAlert(labels, annotations data.Labels, alertState *State, urlStr strin
|
||||
}
|
||||
}
|
||||
|
||||
func FromStateTransitionToPostableAlerts(firingStates []StateTransition, stateManager *Manager, appURL *url.URL) apimodels.PostableAlerts {
|
||||
func FromStateTransitionToPostableAlerts(evaluatedAt time.Time, firingStates []StateTransition, stateManager *Manager, appURL *url.URL) apimodels.PostableAlerts {
|
||||
alerts := apimodels.PostableAlerts{PostableAlerts: make([]models.PostableAlert, 0, len(firingStates))}
|
||||
ts := time.Now()
|
||||
|
||||
sentAlerts := make([]*State, 0, len(firingStates))
|
||||
for _, alertState := range firingStates {
|
||||
if !alertState.NeedsSending(stateManager.ResendDelay) {
|
||||
if !alertState.NeedsSending(stateManager.ResendDelay, stateManager.ResolvedRetention) {
|
||||
continue
|
||||
}
|
||||
alert := StateToPostableAlert(alertState, appURL)
|
||||
@ -153,7 +153,7 @@ func FromStateTransitionToPostableAlerts(firingStates []StateTransition, stateMa
|
||||
if alertState.StateReason == ngModels.StateReasonMissingSeries { // do not put stale state back to state manager
|
||||
continue
|
||||
}
|
||||
alertState.LastSentAt = ts
|
||||
alertState.LastSentAt = &evaluatedAt
|
||||
sentAlerts = append(sentAlerts, alertState.State)
|
||||
}
|
||||
stateManager.Put(sentAlerts)
|
||||
|
@ -9,12 +9,13 @@ import (
|
||||
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/go-openapi/strfmt"
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/data"
|
||||
"github.com/prometheus/alertmanager/api/v2/models"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
alertingModels "github.com/grafana/alerting/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngModels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
@ -267,7 +268,9 @@ func TestStateToPostableAlertFromNodataError(t *testing.T) {
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
alertState := randomTransition(tc.from, tc.to)
|
||||
alertState.Resolved = tc.resolved
|
||||
if tc.resolved {
|
||||
alertState.ResolvedAt = &alertState.LastEvaluationTime
|
||||
}
|
||||
alertState.Labels = data.Labels(standardLabels)
|
||||
result := StateToPostableAlert(alertState, appURL)
|
||||
require.Equal(t, tc.expectedLabels, result.Labels)
|
||||
@ -339,7 +342,7 @@ func randomTransition(from, to eval.State) StateTransition {
|
||||
EndsAt: randomTimeInFuture(),
|
||||
LastEvaluationTime: randomTimeInPast(),
|
||||
EvaluationDuration: randomDuration(),
|
||||
LastSentAt: randomTimeInPast(),
|
||||
LastSentAt: util.Pointer(randomTimeInPast()),
|
||||
Annotations: make(map[string]string),
|
||||
Labels: make(map[string]string),
|
||||
Values: make(map[string]float64),
|
||||
|
@ -39,9 +39,10 @@ type Manager struct {
|
||||
metrics *metrics.State
|
||||
tracer tracing.Tracer
|
||||
|
||||
clock clock.Clock
|
||||
cache *cache
|
||||
ResendDelay time.Duration
|
||||
clock clock.Clock
|
||||
cache *cache
|
||||
ResendDelay time.Duration
|
||||
ResolvedRetention time.Duration
|
||||
|
||||
instanceStore InstanceStore
|
||||
images ImageCapturer
|
||||
@ -73,6 +74,9 @@ type ManagerCfg struct {
|
||||
|
||||
DisableExecution bool
|
||||
|
||||
// Duration for which a resolved alert state transition will continue to be sent to the Alertmanager.
|
||||
ResolvedRetention time.Duration
|
||||
|
||||
Tracer tracing.Tracer
|
||||
Log log.Logger
|
||||
}
|
||||
@ -88,6 +92,7 @@ func NewManager(cfg ManagerCfg, statePersister StatePersister) *Manager {
|
||||
m := &Manager{
|
||||
cache: c,
|
||||
ResendDelay: ResendDelay, // TODO: make this configurable
|
||||
ResolvedRetention: cfg.ResolvedRetention,
|
||||
log: cfg.Log,
|
||||
metrics: cfg.Metrics,
|
||||
instanceStore: cfg.InstanceStore,
|
||||
@ -245,7 +250,11 @@ func (st *Manager) DeleteStateByRuleUID(ctx context.Context, ruleKey ngModels.Al
|
||||
s.SetNormal(reason, startsAt, now)
|
||||
// Set Resolved property so the scheduler knows to send a postable alert
|
||||
// to Alertmanager.
|
||||
s.Resolved = oldState == eval.Alerting || oldState == eval.Error || oldState == eval.NoData
|
||||
if oldState == eval.Alerting || oldState == eval.Error || oldState == eval.NoData {
|
||||
s.ResolvedAt = &now
|
||||
} else {
|
||||
s.ResolvedAt = nil
|
||||
}
|
||||
s.LastEvaluationTime = now
|
||||
s.Values = map[string]float64{}
|
||||
transitions = append(transitions, StateTransition{
|
||||
@ -418,9 +427,15 @@ func (st *Manager) setNextState(ctx context.Context, alertRule *ngModels.AlertRu
|
||||
|
||||
// Set Resolved property so the scheduler knows to send a postable alert
|
||||
// to Alertmanager.
|
||||
currentState.Resolved = oldState == eval.Alerting && currentState.State == eval.Normal
|
||||
newlyResolved := false
|
||||
if oldState == eval.Alerting && currentState.State == eval.Normal {
|
||||
currentState.ResolvedAt = &result.EvaluatedAt
|
||||
newlyResolved = true
|
||||
} else if currentState.State != eval.Normal && currentState.State != eval.Pending { // Retain the last resolved time for Normal->Normal and Normal->Pending.
|
||||
currentState.ResolvedAt = nil
|
||||
}
|
||||
|
||||
if shouldTakeImage(currentState.State, oldState, currentState.Image, currentState.Resolved) {
|
||||
if shouldTakeImage(currentState.State, oldState, currentState.Image, newlyResolved) {
|
||||
image, err := takeImage(ctx, st.images, alertRule)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to take an image",
|
||||
@ -505,7 +520,7 @@ func (st *Manager) deleteStaleStatesFromCache(ctx context.Context, logger log.Lo
|
||||
s.LastEvaluationTime = evaluatedAt
|
||||
|
||||
if oldState == eval.Alerting {
|
||||
s.Resolved = true
|
||||
s.ResolvedAt = &evaluatedAt
|
||||
image, err := takeImage(ctx, st.images, alertRule)
|
||||
if err != nil {
|
||||
logger.Warn("Failed to take an image",
|
||||
|
@ -563,7 +563,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2,
|
||||
LastEvaluationTime: t2,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -622,7 +622,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1051,7 +1051,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1091,7 +1091,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1133,7 +1133,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1175,7 +1175,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -1275,7 +1275,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2,
|
||||
LastEvaluationTime: t2,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1304,6 +1304,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2,
|
||||
LastEvaluationTime: t3,
|
||||
ResolvedAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1935,7 +1936,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -2163,7 +2164,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2191,7 +2192,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2221,7 +2222,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2251,7 +2252,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -2314,7 +2315,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2,
|
||||
LastEvaluationTime: t2,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -2330,6 +2331,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2,
|
||||
LastEvaluationTime: t3,
|
||||
ResolvedAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -3060,7 +3062,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t3,
|
||||
LastEvaluationTime: t3,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t3,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -3480,7 +3482,7 @@ func TestProcessEvalResults_StateTransitions(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2,
|
||||
LastEvaluationTime: t2,
|
||||
Resolved: true,
|
||||
ResolvedAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
@ -7,6 +7,7 @@ import (
|
||||
"fmt"
|
||||
"math"
|
||||
"math/rand"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
"testing"
|
||||
@ -294,7 +295,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
evaluationDuration := 10 * time.Millisecond
|
||||
evaluationInterval := 10 * time.Second
|
||||
|
||||
t1 := time.Time{}.Add(evaluationInterval)
|
||||
t1 := time.Unix(0, 0).Add(evaluationInterval)
|
||||
|
||||
tn := func(n int) time.Time {
|
||||
return t1.Add(time.Duration(n-1) * evaluationInterval)
|
||||
@ -424,6 +425,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t1,
|
||||
EndsAt: t1.Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: t1,
|
||||
LastSentAt: &t1,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -471,6 +473,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2.Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: t2,
|
||||
LastSentAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -501,6 +504,94 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(4),
|
||||
EndsAt: tn(4).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(4),
|
||||
LastSentAt: util.Pointer(tn(4)),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "alerting -> normal resolves and sets ResolvedAt",
|
||||
alertRule: baseRule,
|
||||
evalResults: map[time.Time]eval.Results{
|
||||
t1: {
|
||||
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
|
||||
},
|
||||
t2: {
|
||||
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
|
||||
},
|
||||
},
|
||||
expectedAnnotations: 2,
|
||||
expectedStates: []*state.State{
|
||||
{
|
||||
Labels: labels["system + rule + labels1"],
|
||||
ResultFingerprint: labels1.Fingerprint(),
|
||||
State: eval.Normal,
|
||||
LatestResult: newEvaluation(t2, eval.Normal),
|
||||
StartsAt: t2,
|
||||
EndsAt: t2,
|
||||
LastEvaluationTime: t2,
|
||||
ResolvedAt: &t2,
|
||||
LastSentAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "alerting -> normal -> normal resolves and maintains ResolvedAt",
|
||||
alertRule: baseRule,
|
||||
evalResults: map[time.Time]eval.Results{
|
||||
t1: {
|
||||
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
|
||||
},
|
||||
t2: {
|
||||
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
|
||||
},
|
||||
t3: {
|
||||
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
|
||||
},
|
||||
},
|
||||
expectedAnnotations: 2,
|
||||
expectedStates: []*state.State{
|
||||
{
|
||||
Labels: labels["system + rule + labels1"],
|
||||
ResultFingerprint: labels1.Fingerprint(),
|
||||
State: eval.Normal,
|
||||
LatestResult: newEvaluation(t3, eval.Normal),
|
||||
StartsAt: t2,
|
||||
EndsAt: t2,
|
||||
LastEvaluationTime: t3,
|
||||
ResolvedAt: &t2,
|
||||
LastSentAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
desc: "pending -> alerting -> normal -> pending resolves and resets ResolvedAt at t4",
|
||||
alertRule: baseRuleWith(m.WithForNTimes(1)),
|
||||
evalResults: map[time.Time]eval.Results{
|
||||
t1: {
|
||||
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)),
|
||||
},
|
||||
t2: {
|
||||
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), // Alerting.
|
||||
},
|
||||
t3: {
|
||||
newResult(eval.WithState(eval.Normal), eval.WithLabels(labels1)),
|
||||
},
|
||||
tn(4): {
|
||||
newResult(eval.WithState(eval.Alerting), eval.WithLabels(labels1)), // Pending.
|
||||
},
|
||||
},
|
||||
expectedAnnotations: 4,
|
||||
expectedStates: []*state.State{
|
||||
{
|
||||
Labels: labels["system + rule + labels1"],
|
||||
ResultFingerprint: labels1.Fingerprint(),
|
||||
State: eval.Pending,
|
||||
LatestResult: newEvaluation(tn(4), eval.Alerting),
|
||||
StartsAt: tn(4),
|
||||
EndsAt: tn(4).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(4),
|
||||
ResolvedAt: &t3,
|
||||
LastSentAt: &t3,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -534,6 +625,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(4),
|
||||
EndsAt: tn(4).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(5),
|
||||
LastSentAt: util.Pointer(tn(3)), // 30s resend delay causing the last sent at to be t3.
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -564,6 +656,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(4),
|
||||
EndsAt: tn(4).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(4),
|
||||
LastSentAt: &t3, // Resend delay is 30s, so last sent at is t3.
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -672,6 +765,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(5),
|
||||
EndsAt: tn(5).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(5),
|
||||
LastSentAt: util.Pointer(tn(5)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -696,6 +790,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2.Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: t2,
|
||||
LastSentAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -729,6 +824,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2.Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: t2,
|
||||
LastSentAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -772,6 +868,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2.Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: t2,
|
||||
LastSentAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -808,6 +905,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2.Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: t2,
|
||||
LastSentAt: &t2,
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -839,6 +937,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t3,
|
||||
EndsAt: tn(4).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(4),
|
||||
LastSentAt: &t3, // Resend delay is 30s, so last sent at is t3.
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -870,6 +969,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(4),
|
||||
EndsAt: tn(4).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(4),
|
||||
LastSentAt: util.Pointer(tn(4)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -956,6 +1056,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(5),
|
||||
EndsAt: tn(5).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(5),
|
||||
LastSentAt: util.Pointer(tn(5)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -988,6 +1089,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t2,
|
||||
EndsAt: t2.Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: t2,
|
||||
LastSentAt: &t2,
|
||||
EvaluationDuration: evaluationDuration,
|
||||
Annotations: map[string]string{"annotation": "test", "Error": "[sse.dataQueryError] failed to execute query [A]: this is an error"},
|
||||
},
|
||||
@ -1021,6 +1123,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t3,
|
||||
EndsAt: tn(4).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(4),
|
||||
LastSentAt: &t3, // Resend delay is 30s, so last sent at is t3.
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1052,6 +1155,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(4),
|
||||
EndsAt: tn(4).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(4),
|
||||
LastSentAt: util.Pointer(tn(4)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1139,6 +1243,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(4),
|
||||
EndsAt: tn(6).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(6),
|
||||
LastSentAt: util.Pointer(tn(6)), // After 30s resend delay, last sent at is t6.
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1169,6 +1274,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(8),
|
||||
EndsAt: tn(8).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(8),
|
||||
LastSentAt: util.Pointer(tn(5)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1199,6 +1305,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: tn(6),
|
||||
EndsAt: tn(6).Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: tn(6),
|
||||
LastSentAt: util.Pointer(tn(5)),
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1265,6 +1372,7 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
StartsAt: t3,
|
||||
EndsAt: t3.Add(state.ResendDelay * 4),
|
||||
LastEvaluationTime: t3,
|
||||
LastSentAt: &t1, // Resend delay is 30s, so last sent at is t1.
|
||||
},
|
||||
},
|
||||
},
|
||||
@ -1306,8 +1414,9 @@ func TestProcessEvalResults(t *testing.T) {
|
||||
res[i].EvaluatedAt = evalTime
|
||||
}
|
||||
clk.Set(evalTime)
|
||||
_ = st.ProcessEvalResults(context.Background(), evalTime, tc.alertRule, res, systemLabels)
|
||||
processedStates := st.ProcessEvalResults(context.Background(), evalTime, tc.alertRule, res, systemLabels)
|
||||
results += len(res)
|
||||
_ = state.FromStateTransitionToPostableAlerts(evalTime, processedStates, st, &url.URL{}) // Set LastSentAt.
|
||||
}
|
||||
|
||||
states := st.GetStatesForRuleUID(tc.alertRule.OrgID, tc.alertRule.UID)
|
||||
@ -1670,7 +1779,7 @@ func TestStaleResults(t *testing.T) {
|
||||
assert.Equal(t, models.StateReasonMissingSeries, s.StateReason)
|
||||
assert.Equal(t, clk.Now(), s.EndsAt)
|
||||
if s.CacheID == state2 {
|
||||
assert.Truef(t, s.Resolved, "Returned stale state should have Resolved set to true")
|
||||
assert.Equalf(t, clk.Now(), *s.ResolvedAt, "Returned stale state should have ResolvedAt set")
|
||||
}
|
||||
key, err := s.GetAlertInstanceKey()
|
||||
require.NoError(t, err)
|
||||
@ -1819,11 +1928,11 @@ func TestDeleteStateByRuleUID(t *testing.T) {
|
||||
assert.Equal(t, expectedReason, s.StateReason)
|
||||
if oldState.State == eval.Normal {
|
||||
assert.Equal(t, oldState.StartsAt, s.StartsAt)
|
||||
assert.False(t, s.Resolved)
|
||||
assert.Zero(t, s.ResolvedAt)
|
||||
} else {
|
||||
assert.Equal(t, clk.Now(), s.StartsAt)
|
||||
if oldState.State == eval.Alerting {
|
||||
assert.True(t, s.Resolved)
|
||||
assert.Equal(t, clk.Now(), *s.ResolvedAt)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, clk.Now(), s.EndsAt)
|
||||
@ -1959,11 +2068,11 @@ func TestResetStateByRuleUID(t *testing.T) {
|
||||
assert.Equal(t, models.StateReasonPaused, s.StateReason)
|
||||
if oldState.State == eval.Normal {
|
||||
assert.Equal(t, oldState.StartsAt, s.StartsAt)
|
||||
assert.False(t, s.Resolved)
|
||||
assert.Zero(t, s.ResolvedAt)
|
||||
} else {
|
||||
assert.Equal(t, clk.Now(), s.StartsAt)
|
||||
if oldState.State == eval.Alerting {
|
||||
assert.True(t, s.Resolved)
|
||||
assert.Equal(t, clk.Now(), *s.ResolvedAt)
|
||||
}
|
||||
}
|
||||
assert.Equal(t, clk.Now(), s.EndsAt)
|
||||
|
@ -45,10 +45,6 @@ type State struct {
|
||||
// can still contain the results of previous evaluations.
|
||||
Error error
|
||||
|
||||
// Resolved is set to true if this state is the transitional state between Firing and Normal.
|
||||
// All subsequent states will be false until the next transition from Firing to Normal.
|
||||
Resolved bool
|
||||
|
||||
// Image contains an optional image for the state. It tends to be included in notifications
|
||||
// as a visualization to show why the alert fired.
|
||||
Image *models.Image
|
||||
@ -65,9 +61,15 @@ type State struct {
|
||||
// conditions.
|
||||
Values map[string]float64
|
||||
|
||||
StartsAt time.Time
|
||||
EndsAt time.Time
|
||||
LastSentAt time.Time
|
||||
StartsAt time.Time
|
||||
// EndsAt is different from the Prometheus EndsAt as EndsAt is updated for both Normal states
|
||||
// and states that have been resolved. It cannot be used to determine when a state was resolved.
|
||||
EndsAt time.Time
|
||||
// ResolvedAt is set when the state is first resolved. That is to say, when the state first transitions
|
||||
// from Alerting, NoData, or Error to Normal. It is reset to zero when the state transitions from Normal
|
||||
// to any other state.
|
||||
ResolvedAt *time.Time
|
||||
LastSentAt *time.Time
|
||||
LastEvaluationString string
|
||||
LastEvaluationTime time.Time
|
||||
EvaluationDuration time.Duration
|
||||
@ -134,14 +136,6 @@ func (a *State) SetNormal(reason string, startsAt, endsAt time.Time) {
|
||||
a.Error = nil
|
||||
}
|
||||
|
||||
// Resolve sets the State to Normal. It updates the StateReason, the end time, and sets Resolved to true.
|
||||
func (a *State) Resolve(reason string, endsAt time.Time) {
|
||||
a.State = eval.Normal
|
||||
a.StateReason = reason
|
||||
a.Resolved = true
|
||||
a.EndsAt = endsAt
|
||||
}
|
||||
|
||||
// Maintain updates the end time using the most recent evaluation.
|
||||
func (a *State) Maintain(interval int64, evaluatedAt time.Time) {
|
||||
a.EndsAt = nextEndsTime(interval, evaluatedAt)
|
||||
@ -400,19 +394,31 @@ func resultKeepLast(state *State, rule *models.AlertRule, result eval.Result, lo
|
||||
}
|
||||
}
|
||||
|
||||
func (a *State) NeedsSending(resendDelay time.Duration) bool {
|
||||
switch a.State {
|
||||
case eval.Pending:
|
||||
// We do not send notifications for pending states
|
||||
// NeedsSending returns true if the given state needs to be sent to the Alertmanager.
|
||||
// Reasons for sending include:
|
||||
// - The state has been resolved since the last notification.
|
||||
// - The state is firing and the last notification was sent at least resendDelay ago.
|
||||
// - The state was resolved within the resolvedRetention period, and the last notification was sent at least resendDelay ago.
|
||||
func (a *State) NeedsSending(resendDelay time.Duration, resolvedRetention time.Duration) bool {
|
||||
if a.State == eval.Pending {
|
||||
// We do not send notifications for pending states.
|
||||
return false
|
||||
case eval.Normal:
|
||||
// We should send a notification if the state is Normal because it was resolved
|
||||
return a.Resolved
|
||||
default:
|
||||
// We should send, and re-send notifications, each time LastSentAt is <= LastEvaluationTime + resendDelay
|
||||
nextSent := a.LastSentAt.Add(resendDelay)
|
||||
return nextSent.Before(a.LastEvaluationTime) || nextSent.Equal(a.LastEvaluationTime)
|
||||
}
|
||||
|
||||
// We should send a notification if the state has been resolved since the last notification.
|
||||
if a.ResolvedAt != nil && (a.LastSentAt == nil || a.ResolvedAt.After(*a.LastSentAt)) {
|
||||
return true
|
||||
}
|
||||
|
||||
// For normal states, we should only be sending if this is a resolved notification or a re-send of the resolved
|
||||
// notification within the resolvedRetention period.
|
||||
if a.State == eval.Normal && (a.ResolvedAt == nil || a.LastEvaluationTime.Sub(*a.ResolvedAt) > resolvedRetention) {
|
||||
return false
|
||||
}
|
||||
|
||||
// We should send, and re-send notifications, each time LastSentAt is <= LastEvaluationTime + resendDelay.
|
||||
// This can include normal->normal transitions that were resolved in recent past evaluations.
|
||||
return a.LastSentAt == nil || !a.LastSentAt.Add(resendDelay).After(a.LastEvaluationTime)
|
||||
}
|
||||
|
||||
func (a *State) Equals(b *State) bool {
|
||||
|
@ -11,11 +11,12 @@ import (
|
||||
"github.com/benbjohnson/clock"
|
||||
"github.com/golang/mock/gomock"
|
||||
"github.com/google/uuid"
|
||||
"github.com/grafana/alerting/models"
|
||||
"github.com/prometheus/common/model"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
"github.com/grafana/alerting/models"
|
||||
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/eval"
|
||||
ngmodels "github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
@ -350,10 +351,11 @@ func TestEnd(t *testing.T) {
|
||||
func TestNeedsSending(t *testing.T) {
|
||||
evaluationTime, _ := time.Parse("2006-01-02", "2021-03-25")
|
||||
testCases := []struct {
|
||||
name string
|
||||
resendDelay time.Duration
|
||||
expected bool
|
||||
testState *State
|
||||
name string
|
||||
resendDelay time.Duration
|
||||
resolvedRetention time.Duration
|
||||
expected bool
|
||||
testState *State
|
||||
}{
|
||||
{
|
||||
name: "state: alerting and LastSentAt before LastEvaluationTime + ResendDelay",
|
||||
@ -362,7 +364,7 @@ func TestNeedsSending(t *testing.T) {
|
||||
testState: &State{
|
||||
State: eval.Alerting,
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime.Add(-2 * time.Minute),
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-2 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -372,7 +374,7 @@ func TestNeedsSending(t *testing.T) {
|
||||
testState: &State{
|
||||
State: eval.Alerting,
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -382,7 +384,7 @@ func TestNeedsSending(t *testing.T) {
|
||||
testState: &State{
|
||||
State: eval.Alerting,
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime.Add(-1 * time.Minute),
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -400,18 +402,54 @@ func TestNeedsSending(t *testing.T) {
|
||||
testState: &State{
|
||||
State: eval.Alerting,
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "state: normal + resolved should send without waiting",
|
||||
name: "state: normal + resolved should send without waiting if ResolvedAt > LastSentAt",
|
||||
resendDelay: 1 * time.Minute,
|
||||
expected: true,
|
||||
testState: &State{
|
||||
State: eval.Normal,
|
||||
Resolved: true,
|
||||
ResolvedAt: util.Pointer(evaluationTime),
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "state: normal + recently resolved should send with wait",
|
||||
resendDelay: 1 * time.Minute,
|
||||
resolvedRetention: 15 * time.Minute,
|
||||
expected: true,
|
||||
testState: &State{
|
||||
State: eval.Normal,
|
||||
ResolvedAt: util.Pointer(evaluationTime.Add(-2 * time.Minute)),
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "state: normal + recently resolved should not send without wait",
|
||||
resendDelay: 2 * time.Minute,
|
||||
resolvedRetention: 15 * time.Minute,
|
||||
expected: false,
|
||||
testState: &State{
|
||||
State: eval.Normal,
|
||||
ResolvedAt: util.Pointer(evaluationTime.Add(-2 * time.Minute)),
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "state: normal + not recently resolved should not send even with wait",
|
||||
resendDelay: 1 * time.Minute,
|
||||
resolvedRetention: 15 * time.Minute,
|
||||
expected: false,
|
||||
testState: &State{
|
||||
State: eval.Normal,
|
||||
ResolvedAt: util.Pointer(evaluationTime.Add(-16 * time.Minute)),
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -420,9 +458,9 @@ func TestNeedsSending(t *testing.T) {
|
||||
expected: false,
|
||||
testState: &State{
|
||||
State: eval.Normal,
|
||||
Resolved: false,
|
||||
ResolvedAt: util.Pointer(time.Time{}),
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime.Add(-1 * time.Minute),
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -432,7 +470,7 @@ func TestNeedsSending(t *testing.T) {
|
||||
testState: &State{
|
||||
State: eval.NoData,
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime.Add(-1 * time.Minute),
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -442,7 +480,7 @@ func TestNeedsSending(t *testing.T) {
|
||||
testState: &State{
|
||||
State: eval.NoData,
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime.Add(-time.Duration(rand.Int63n(59)+1) * time.Second),
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-time.Duration(rand.Int63n(59)+1) * time.Second)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -452,7 +490,7 @@ func TestNeedsSending(t *testing.T) {
|
||||
testState: &State{
|
||||
State: eval.Error,
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime.Add(-1 * time.Minute),
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-1 * time.Minute)),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -462,14 +500,14 @@ func TestNeedsSending(t *testing.T) {
|
||||
testState: &State{
|
||||
State: eval.Error,
|
||||
LastEvaluationTime: evaluationTime,
|
||||
LastSentAt: evaluationTime.Add(-time.Duration(rand.Int63n(59)+1) * time.Second),
|
||||
LastSentAt: util.Pointer(evaluationTime.Add(-time.Duration(rand.Int63n(59)+1) * time.Second)),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range testCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
assert.Equal(t, tc.expected, tc.testState.NeedsSending(tc.resendDelay))
|
||||
assert.Equal(t, tc.expected, tc.testState.NeedsSending(tc.resendDelay, tc.resolvedRetention))
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -531,13 +569,6 @@ func TestGetLastEvaluationValuesForCondition(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestResolve(t *testing.T) {
|
||||
s := State{State: eval.Alerting, EndsAt: time.Now().Add(time.Minute)}
|
||||
expected := State{State: eval.Normal, StateReason: "This is a reason", EndsAt: time.Now(), Resolved: true}
|
||||
s.Resolve("This is a reason", expected.EndsAt)
|
||||
assert.Equal(t, expected, s)
|
||||
}
|
||||
|
||||
func TestShouldTakeImage(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
|
@ -6,11 +6,12 @@ import (
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
alertingCluster "github.com/grafana/alerting/cluster"
|
||||
dstls "github.com/grafana/dskit/crypto/tls"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend/gtime"
|
||||
"gopkg.in/ini.v1"
|
||||
|
||||
alertingCluster "github.com/grafana/alerting/cluster"
|
||||
|
||||
"github.com/grafana/grafana/pkg/util"
|
||||
)
|
||||
|
||||
@ -113,6 +114,9 @@ type UnifiedAlertingSettings struct {
|
||||
|
||||
// Retention period for Alertmanager notification log entries.
|
||||
NotificationLogRetention time.Duration
|
||||
|
||||
// Duration for which a resolved alert state transition will continue to be sent to the Alertmanager.
|
||||
ResolvedAlertRetention time.Duration
|
||||
}
|
||||
|
||||
type RecordingRuleSettings struct {
|
||||
@ -435,6 +439,11 @@ func (cfg *Cfg) ReadUnifiedAlertingSettings(iniFile *ini.File) error {
|
||||
return err
|
||||
}
|
||||
|
||||
uaCfg.ResolvedAlertRetention, err = gtime.ParseDuration(valueAsString(ua, "resolved_alert_retention", (15 * time.Minute).String()))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
cfg.UnifiedAlerting = uaCfg
|
||||
return nil
|
||||
}
|
||||
|
@ -0,0 +1,541 @@
|
||||
package timeinterval
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/prometheus/alertmanager/config"
|
||||
"github.com/stretchr/testify/require"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/generated/clientset/versioned"
|
||||
"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/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/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 TestIntegrationResourceIdentifier(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().TimeIntervals("default")
|
||||
|
||||
newInterval := &v0alpha1.TimeInterval{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v0alpha1.TimeIntervalSpec{
|
||||
Name: "time-newInterval",
|
||||
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
|
||||
},
|
||||
}
|
||||
|
||||
t.Run("create should fail if object name is specified", func(t *testing.T) {
|
||||
interval := newInterval.DeepCopy()
|
||||
interval.Name = "time-newInterval"
|
||||
_, err := client.Create(ctx, interval, v1.CreateOptions{})
|
||||
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest but got %s", err)
|
||||
})
|
||||
|
||||
var resourceID string
|
||||
t.Run("create should succeed and provide resource name", func(t *testing.T) {
|
||||
actual, err := client.Create(ctx, newInterval, v1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotEmptyf(t, actual.Name, "Resource name should not be empty")
|
||||
require.NotEmptyf(t, actual.UID, "Resource UID should not be empty")
|
||||
resourceID = actual.Name
|
||||
})
|
||||
|
||||
var existingInterval *v0alpha1.TimeInterval
|
||||
t.Run("resource should be available by the identifier", func(t *testing.T) {
|
||||
actual, err := client.Get(ctx, resourceID, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotEmptyf(t, actual.Name, "Resource name should not be empty")
|
||||
require.Equal(t, newInterval.Spec, actual.Spec)
|
||||
existingInterval = actual
|
||||
})
|
||||
|
||||
t.Run("update should fail if name in the specification changes", func(t *testing.T) {
|
||||
if existingInterval == nil {
|
||||
t.Skip()
|
||||
}
|
||||
updated := existingInterval.DeepCopy()
|
||||
updated.Spec.Name = "another-newInterval"
|
||||
_, err := client.Update(ctx, updated, v1.UpdateOptions{})
|
||||
require.Truef(t, errors.IsBadRequest(err), "Expected BadRequest but got %s", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationTimeIntervalAccessControl(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
|
||||
canCreate bool
|
||||
canDelete bool
|
||||
}
|
||||
|
||||
reader := helper.CreateUser("IntervalsReader", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
{
|
||||
Actions: []string{
|
||||
accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
||||
},
|
||||
},
|
||||
})
|
||||
writer := helper.CreateUser("IntervalsWriter", "Org1", org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
{
|
||||
Actions: []string{
|
||||
accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
||||
accesscontrol.ActionAlertingNotificationsTimeIntervalsWrite,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
deleter := helper.CreateUser("IntervalsDeleter", apis.Org1, org.RoleNone, []resourcepermissions.SetResourcePermissionCommand{
|
||||
{
|
||||
Actions: []string{
|
||||
accesscontrol.ActionAlertingNotificationsTimeIntervalsRead,
|
||||
accesscontrol.ActionAlertingNotificationsTimeIntervalsDelete,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
testCases := []testCase{
|
||||
{
|
||||
user: org1.Admin,
|
||||
canRead: true,
|
||||
canUpdate: true,
|
||||
canCreate: true,
|
||||
canDelete: true,
|
||||
},
|
||||
{
|
||||
user: org1.Editor,
|
||||
canRead: true,
|
||||
canUpdate: true,
|
||||
canCreate: true,
|
||||
canDelete: true,
|
||||
},
|
||||
{
|
||||
user: org1.Viewer,
|
||||
canRead: true,
|
||||
},
|
||||
{
|
||||
user: reader,
|
||||
canRead: true,
|
||||
},
|
||||
{
|
||||
user: writer,
|
||||
canRead: true,
|
||||
canCreate: true,
|
||||
canUpdate: true,
|
||||
},
|
||||
{
|
||||
user: deleter,
|
||||
canRead: true,
|
||||
canDelete: true,
|
||||
},
|
||||
}
|
||||
|
||||
admin := org1.Admin
|
||||
adminK8sClient, err := versioned.NewForConfig(admin.NewRestConfig())
|
||||
require.NoError(t, err)
|
||||
adminClient := adminK8sClient.NotificationsV0alpha1().TimeIntervals("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().TimeIntervals("default")
|
||||
|
||||
var expected = &v0alpha1.TimeInterval{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
Annotations: map[string]string{
|
||||
"grafana.com/provenance": "",
|
||||
},
|
||||
},
|
||||
Spec: v0alpha1.TimeIntervalSpec{
|
||||
Name: fmt.Sprintf("time-interval-1-%s", tc.user.Identity.GetLogin()),
|
||||
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
|
||||
},
|
||||
}
|
||||
d, err := json.Marshal(expected)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.canCreate {
|
||||
t.Run("should be able to create time interval", func(t *testing.T) {
|
||||
actual, err := client.Create(ctx, expected, v1.CreateOptions{})
|
||||
require.NoErrorf(t, err, "Payload %s", string(d))
|
||||
require.Equal(t, expected.Spec, actual.Spec)
|
||||
|
||||
t.Run("should fail if already exists", func(t *testing.T) {
|
||||
_, err := client.Create(ctx, actual, v1.CreateOptions{})
|
||||
require.Truef(t, errors.IsBadRequest(err), "expected bad request but got %s", err)
|
||||
})
|
||||
|
||||
expected = actual
|
||||
})
|
||||
} else {
|
||||
t.Run("should be forbidden to create", func(t *testing.T) {
|
||||
_, err := client.Create(ctx, expected, v1.CreateOptions{})
|
||||
require.Truef(t, errors.IsForbidden(err), "Payload %s", string(d))
|
||||
})
|
||||
|
||||
// create resource to proceed with other tests
|
||||
expected, err = adminClient.Create(ctx, expected, v1.CreateOptions{})
|
||||
require.NoErrorf(t, err, "Payload %s", string(d))
|
||||
require.NotNil(t, expected)
|
||||
}
|
||||
|
||||
if tc.canRead {
|
||||
t.Run("should be able to list time intervals", func(t *testing.T) {
|
||||
list, err := client.List(ctx, v1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Items, 1)
|
||||
})
|
||||
|
||||
t.Run("should be able to read time interval by resource identifier", func(t *testing.T) {
|
||||
got, err := client.Get(ctx, expected.Name, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, expected, got)
|
||||
|
||||
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 time intervals", func(t *testing.T) {
|
||||
_, err := client.List(ctx, v1.ListOptions{})
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
|
||||
t.Run("should be forbidden to read time interval by name", func(t *testing.T) {
|
||||
_, err := client.Get(ctx, expected.Name, v1.GetOptions{})
|
||||
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.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
updatedExpected := expected.DeepCopy()
|
||||
updatedExpected.Spec.TimeIntervals = v0alpha1.IntervalGenerator{}.GenerateMany(2)
|
||||
|
||||
d, err = json.Marshal(updatedExpected)
|
||||
require.NoError(t, err)
|
||||
|
||||
if tc.canUpdate {
|
||||
t.Run("should be able to update time interval", func(t *testing.T) {
|
||||
updated, err := client.Update(ctx, updatedExpected, 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 := updatedExpected.DeepCopy()
|
||||
up.Name = "notFound"
|
||||
_, err := client.Update(ctx, up, v1.UpdateOptions{})
|
||||
require.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
t.Run("should be forbidden to update time interval", func(t *testing.T) {
|
||||
_, err := client.Update(ctx, updatedExpected, v1.UpdateOptions{})
|
||||
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 := updatedExpected.DeepCopy()
|
||||
up.Name = "notFound"
|
||||
_, err := client.Update(ctx, up, v1.UpdateOptions{})
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
deleteOptions := v1.DeleteOptions{Preconditions: &v1.Preconditions{ResourceVersion: util.Pointer(expected.ResourceVersion)}}
|
||||
|
||||
if tc.canDelete {
|
||||
t.Run("should be able to delete time interval", func(t *testing.T) {
|
||||
err := client.Delete(ctx, expected.Name, 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.Truef(t, errors.IsNotFound(err), "Should get NotFound error but got: %s", err)
|
||||
})
|
||||
})
|
||||
} else {
|
||||
t.Run("should be forbidden to delete time interval", func(t *testing.T) {
|
||||
err := client.Delete(ctx, expected.Name, deleteOptions)
|
||||
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.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
})
|
||||
require.NoError(t, adminClient.Delete(ctx, expected.Name, v1.DeleteOptions{}))
|
||||
}
|
||||
|
||||
if tc.canRead {
|
||||
t.Run("should get empty list if no mute timings", func(t *testing.T) {
|
||||
list, err := client.List(ctx, v1.ListOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Len(t, list.Items, 0)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestIntegrationTimeIntervalProvisioning(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().TimeIntervals("default")
|
||||
|
||||
env := helper.GetEnv()
|
||||
ac := acimpl.ProvideAccessControl(env.FeatureToggles)
|
||||
db, err := store.ProvideDBStore(env.Cfg, env.FeatureToggles, env.SQLStore, &foldertest.FakeService{}, &dashboards.FakeDashboardService{}, ac)
|
||||
require.NoError(t, err)
|
||||
|
||||
created, err := adminClient.Create(ctx, &v0alpha1.TimeInterval{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v0alpha1.TimeIntervalSpec{
|
||||
Name: "time-interval-1",
|
||||
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
|
||||
},
|
||||
}, v1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "", created.Annotations["grafana.com/provenance"])
|
||||
|
||||
t.Run("should provide provenance status", func(t *testing.T) {
|
||||
require.NoError(t, db.SetProvenance(ctx, &definitions.MuteTimeInterval{
|
||||
MuteTimeInterval: config.MuteTimeInterval{
|
||||
Name: created.Spec.Name,
|
||||
},
|
||||
}, admin.Identity.GetOrgID(), "API"))
|
||||
|
||||
got, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "API", got.Annotations["grafana.com/provenance"])
|
||||
})
|
||||
t.Run("should not let update if provisioned", func(t *testing.T) {
|
||||
updated := created.DeepCopy()
|
||||
updated.Spec.TimeIntervals = v0alpha1.IntervalGenerator{}.GenerateMany(2)
|
||||
|
||||
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
||||
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, created.Name, v1.DeleteOptions{})
|
||||
require.Truef(t, errors.IsForbidden(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationTimeIntervalOptimisticConcurrency(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().TimeIntervals("default")
|
||||
|
||||
interval := v0alpha1.TimeInterval{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v0alpha1.TimeIntervalSpec{
|
||||
Name: "time-interval",
|
||||
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
|
||||
},
|
||||
}
|
||||
|
||||
created, err := adminClient.Create(ctx, &interval, v1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, created)
|
||||
require.NotEmpty(t, created.ResourceVersion)
|
||||
|
||||
t.Run("should forbid if version does not match", func(t *testing.T) {
|
||||
updated := created.DeepCopy()
|
||||
updated.ResourceVersion = "test"
|
||||
_, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
||||
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 := created.DeepCopy()
|
||||
updated.Spec.TimeIntervals = v0alpha1.IntervalGenerator{}.GenerateMany(2)
|
||||
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) {
|
||||
updated := created.DeepCopy()
|
||||
updated.ResourceVersion = ""
|
||||
updated.Spec.TimeIntervals = v0alpha1.IntervalGenerator{}.GenerateMany(2)
|
||||
|
||||
actualUpdated, err := adminClient.Update(ctx, updated, v1.UpdateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.EqualValues(t, updated.Spec, actualUpdated.Spec)
|
||||
require.NotEqual(t, created.ResourceVersion, actualUpdated.ResourceVersion)
|
||||
})
|
||||
t.Run("should fail to delete if version does not match", func(t *testing.T) {
|
||||
actual, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
||||
Preconditions: &v1.Preconditions{
|
||||
ResourceVersion: util.Pointer("something"),
|
||||
},
|
||||
})
|
||||
require.Truef(t, errors.IsConflict(err), "should get Forbidden error but got %s", err)
|
||||
})
|
||||
t.Run("should succeed if version matches", func(t *testing.T) {
|
||||
actual, err := adminClient.Get(ctx, created.Name, v1.GetOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
||||
Preconditions: &v1.Preconditions{
|
||||
ResourceVersion: util.Pointer(actual.ResourceVersion),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
t.Run("should succeed if version is empty", func(t *testing.T) {
|
||||
actual, err := adminClient.Create(ctx, &interval, v1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
|
||||
err = adminClient.Delete(ctx, actual.Name, v1.DeleteOptions{
|
||||
Preconditions: &v1.Preconditions{
|
||||
ResourceVersion: util.Pointer(actual.ResourceVersion),
|
||||
},
|
||||
})
|
||||
require.NoError(t, err)
|
||||
})
|
||||
}
|
||||
|
||||
func TestIntegrationTimeIntervalPatch(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().TimeIntervals("default")
|
||||
|
||||
interval := v0alpha1.TimeInterval{
|
||||
ObjectMeta: v1.ObjectMeta{
|
||||
Namespace: "default",
|
||||
},
|
||||
Spec: v0alpha1.TimeIntervalSpec{
|
||||
Name: "time-interval",
|
||||
TimeIntervals: v0alpha1.IntervalGenerator{}.GenerateMany(2),
|
||||
},
|
||||
}
|
||||
|
||||
current, err := adminClient.Create(ctx, &interval, v1.CreateOptions{})
|
||||
require.NoError(t, err)
|
||||
require.NotNil(t, current)
|
||||
require.NotEmpty(t, current.ResourceVersion)
|
||||
|
||||
t.Run("should patch with merge patch", func(t *testing.T) {
|
||||
patch := `{
|
||||
"spec": {
|
||||
"time_intervals" : []
|
||||
}
|
||||
}`
|
||||
|
||||
result, err := adminClient.Patch(ctx, current.Name, types.MergePatchType, []byte(patch), v1.PatchOptions{})
|
||||
require.NoError(t, err)
|
||||
require.Empty(t, result.Spec.TimeIntervals)
|
||||
current = result
|
||||
})
|
||||
|
||||
t.Run("should patch with json patch", func(t *testing.T) {
|
||||
expected := v0alpha1.IntervalGenerator{}.Generate()
|
||||
|
||||
patch := []map[string]interface{}{
|
||||
{
|
||||
"op": "add",
|
||||
"path": "/spec/time_intervals/-",
|
||||
"value": expected,
|
||||
},
|
||||
}
|
||||
|
||||
patchData, err := json.Marshal(patch)
|
||||
require.NoError(t, err)
|
||||
|
||||
result, err := adminClient.Patch(ctx, current.Name, types.JSONPatchType, patchData, v1.PatchOptions{})
|
||||
require.NoError(t, err)
|
||||
expectedSpec := *current.Spec.DeepCopy()
|
||||
expectedSpec.TimeIntervals = []v0alpha1.Interval{
|
||||
expected,
|
||||
}
|
||||
require.EqualValues(t, expectedSpec, result.Spec)
|
||||
current = result
|
||||
})
|
||||
}
|
@ -28,8 +28,11 @@ import (
|
||||
"github.com/grafana/grafana/pkg/infra/localcache"
|
||||
"github.com/grafana/grafana/pkg/infra/tracing"
|
||||
"github.com/grafana/grafana/pkg/server"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol"
|
||||
"github.com/grafana/grafana/pkg/services/accesscontrol/resourcepermissions"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
"github.com/grafana/grafana/pkg/services/org"
|
||||
"github.com/grafana/grafana/pkg/services/org/orgimpl"
|
||||
"github.com/grafana/grafana/pkg/services/quota/quotaimpl"
|
||||
@ -40,6 +43,8 @@ import (
|
||||
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||
)
|
||||
|
||||
const Org1 = "Org1"
|
||||
|
||||
type K8sTestHelper struct {
|
||||
t *testing.T
|
||||
env server.TestEnv
|
||||
@ -63,7 +68,7 @@ func NewK8sTestHelper(t *testing.T, opts testinfra.GrafanaOpts) *K8sTestHelper {
|
||||
namespacer: request.GetNamespaceMapper(nil),
|
||||
}
|
||||
|
||||
c.Org1 = c.createTestUsers("Org1")
|
||||
c.Org1 = c.createTestUsers(Org1)
|
||||
c.OrgB = c.createTestUsers("OrgB")
|
||||
|
||||
c.loadAPIGroups()
|
||||
@ -88,6 +93,10 @@ func (c *K8sTestHelper) loadAPIGroups() {
|
||||
}
|
||||
}
|
||||
|
||||
func (c *K8sTestHelper) GetEnv() server.TestEnv {
|
||||
return c.env
|
||||
}
|
||||
|
||||
func (c *K8sTestHelper) Shutdown() {
|
||||
err := c.env.Server.Shutdown(context.Background(), "done")
|
||||
require.NoError(c.t, err)
|
||||
@ -172,7 +181,7 @@ func (c *K8sResourceClient) SanitizeJSON(v *unstructured.Unstructured) string {
|
||||
}
|
||||
|
||||
out, err := json.MarshalIndent(copy, "", " ")
|
||||
//fmt.Printf("%s", out)
|
||||
// fmt.Printf("%s", out)
|
||||
require.NoError(c.t, err)
|
||||
return string(out)
|
||||
}
|
||||
@ -374,7 +383,16 @@ func (c *K8sTestHelper) LoadYAMLOrJSON(body string) *unstructured.Unstructured {
|
||||
return &unstructured.Unstructured{Object: unstructuredMap}
|
||||
}
|
||||
|
||||
func (c K8sTestHelper) createTestUsers(orgName string) OrgUsers {
|
||||
func (c *K8sTestHelper) createTestUsers(orgName string) OrgUsers {
|
||||
c.t.Helper()
|
||||
return OrgUsers{
|
||||
Admin: c.CreateUser("admin", orgName, org.RoleAdmin, nil),
|
||||
Editor: c.CreateUser("editor", orgName, org.RoleEditor, nil),
|
||||
Viewer: c.CreateUser("viewer", orgName, org.RoleViewer, nil),
|
||||
}
|
||||
}
|
||||
|
||||
func (c *K8sTestHelper) CreateUser(name string, orgName string, basicRole org.RoleType, permissions []resourcepermissions.SetResourcePermissionCommand) User {
|
||||
c.t.Helper()
|
||||
|
||||
store := c.env.SQLStore
|
||||
@ -389,9 +407,17 @@ func (c K8sTestHelper) createTestUsers(orgName string) OrgUsers {
|
||||
require.NoError(c.t, err)
|
||||
|
||||
orgId := int64(1)
|
||||
if orgName != "Org1" {
|
||||
orgId, err = orgService.GetOrCreate(context.Background(), orgName)
|
||||
require.NoError(c.t, err)
|
||||
if orgName != Org1 {
|
||||
o, err := orgService.GetByName(context.Background(), &org.GetOrgByNameQuery{Name: orgName})
|
||||
if err != nil {
|
||||
if !org.ErrOrgNotFound.Is(err) {
|
||||
require.NoError(c.t, err)
|
||||
}
|
||||
orgId, err = orgService.GetOrCreate(context.Background(), orgName)
|
||||
require.NoError(c.t, err)
|
||||
} else {
|
||||
orgId = o.ID
|
||||
}
|
||||
}
|
||||
c.env.Cfg.AutoAssignOrg = true
|
||||
c.env.Cfg.AutoAssignOrgId = int(orgId)
|
||||
@ -407,37 +433,52 @@ func (c K8sTestHelper) createTestUsers(orgName string) OrgUsers {
|
||||
require.NoError(c.t, err)
|
||||
|
||||
baseUrl := fmt.Sprintf("http://%s", c.env.Server.HTTPServer.Listener.Addr())
|
||||
createUser := func(key string, role org.RoleType) User {
|
||||
u, err := userSvc.Create(context.Background(), &user.CreateUserCommand{
|
||||
DefaultOrgRole: string(role),
|
||||
Password: user.Password(key),
|
||||
Login: fmt.Sprintf("%s-%d", key, orgId),
|
||||
OrgID: orgId,
|
||||
})
|
||||
require.NoError(c.t, err)
|
||||
require.Equal(c.t, orgId, u.OrgID)
|
||||
require.True(c.t, u.ID > 0)
|
||||
|
||||
s, err := userSvc.GetSignedInUser(context.Background(), &user.GetSignedInUserQuery{
|
||||
UserID: u.ID,
|
||||
Login: u.Login,
|
||||
Email: u.Email,
|
||||
OrgID: orgId,
|
||||
})
|
||||
require.NoError(c.t, err)
|
||||
require.Equal(c.t, orgId, s.OrgID)
|
||||
require.Equal(c.t, role, s.OrgRole) // make sure the role was set properly
|
||||
u, err := userSvc.Create(context.Background(), &user.CreateUserCommand{
|
||||
DefaultOrgRole: string(basicRole),
|
||||
Password: user.Password(name),
|
||||
Login: fmt.Sprintf("%s-%d", name, orgId),
|
||||
OrgID: orgId,
|
||||
})
|
||||
require.NoError(c.t, err)
|
||||
require.Equal(c.t, orgId, u.OrgID)
|
||||
require.True(c.t, u.ID > 0)
|
||||
|
||||
return User{
|
||||
Identity: s,
|
||||
password: key,
|
||||
baseURL: baseUrl,
|
||||
}
|
||||
s, err := userSvc.GetSignedInUser(context.Background(), &user.GetSignedInUserQuery{
|
||||
UserID: u.ID,
|
||||
Login: u.Login,
|
||||
Email: u.Email,
|
||||
OrgID: orgId,
|
||||
})
|
||||
require.NoError(c.t, err)
|
||||
require.Equal(c.t, orgId, s.OrgID)
|
||||
require.Equal(c.t, basicRole, s.OrgRole) // make sure the role was set properly
|
||||
|
||||
usr := User{
|
||||
Identity: s,
|
||||
password: name,
|
||||
baseURL: baseUrl,
|
||||
}
|
||||
return OrgUsers{
|
||||
Admin: createUser("admin", org.RoleAdmin),
|
||||
Editor: createUser("editor", org.RoleEditor),
|
||||
Viewer: createUser("viewer", org.RoleViewer),
|
||||
|
||||
if len(permissions) > 0 {
|
||||
c.SetPermissions(usr, permissions)
|
||||
}
|
||||
|
||||
return usr
|
||||
}
|
||||
|
||||
func (c *K8sTestHelper) SetPermissions(user User, permissions []resourcepermissions.SetResourcePermissionCommand) {
|
||||
id, err := user.Identity.GetID().UserID()
|
||||
require.NoError(c.t, err)
|
||||
|
||||
permissionsStore := resourcepermissions.NewStore(c.env.Cfg, c.env.SQLStore, featuremgmt.WithFeatures())
|
||||
|
||||
for _, permission := range permissions {
|
||||
_, err := permissionsStore.SetUserResourcePermission(context.Background(),
|
||||
user.Identity.GetOrgID(),
|
||||
accesscontrol.User{ID: id},
|
||||
permission, nil)
|
||||
require.NoError(c.t, err)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -41,6 +41,7 @@ export function QueryTemplatesList() {
|
||||
const datasourceType = getDatasourceSrv().getInstanceSettings(datasourceRef)?.meta.name || '';
|
||||
return {
|
||||
index: index.toString(),
|
||||
uid: queryTemplate.uid,
|
||||
datasourceRef,
|
||||
datasourceType,
|
||||
createdAtTimestamp: queryTemplate?.createdAtTimestamp || 0,
|
||||
|
@ -1,16 +1,67 @@
|
||||
import React from 'react';
|
||||
|
||||
import { reportInteraction, getAppEvents } from '@grafana/runtime';
|
||||
import { DataQuery } from '@grafana/schema';
|
||||
import { IconButton } from '@grafana/ui';
|
||||
import { notifyApp } from 'app/core/actions';
|
||||
import { createSuccessNotification } from 'app/core/copy/appNotification';
|
||||
import { t } from 'app/core/internationalization';
|
||||
import { useDeleteQueryTemplateMutation } from 'app/features/query-library';
|
||||
import { dispatch } from 'app/store/store';
|
||||
import { ShowConfirmModalEvent } from 'app/types/events';
|
||||
|
||||
import ExploreRunQueryButton from '../../ExploreRunQueryButton';
|
||||
|
||||
import { useQueryLibraryListStyles } from './styles';
|
||||
|
||||
interface ActionsCellProps {
|
||||
queryUid?: string;
|
||||
query?: DataQuery;
|
||||
rootDatasourceUid?: string;
|
||||
}
|
||||
|
||||
function ActionsCell({ query, rootDatasourceUid }: ActionsCellProps) {
|
||||
return <ExploreRunQueryButton queries={query ? [query] : []} rootDatasourceUid={rootDatasourceUid} />;
|
||||
function ActionsCell({ query, rootDatasourceUid, queryUid }: ActionsCellProps) {
|
||||
const [deleteQueryTemplate] = useDeleteQueryTemplateMutation();
|
||||
const styles = useQueryLibraryListStyles();
|
||||
|
||||
const onDeleteQuery = (queryUid: string) => {
|
||||
const performDelete = (queryUid: string) => {
|
||||
deleteQueryTemplate({ uid: queryUid });
|
||||
dispatch(notifyApp(createSuccessNotification(t('explore.query-library.query-deleted', 'Query deleted'))));
|
||||
reportInteraction('grafana_explore_query_library_deleted');
|
||||
};
|
||||
|
||||
getAppEvents().publish(
|
||||
new ShowConfirmModalEvent({
|
||||
title: t('explore.query-library.delete-query-title', 'Delete query'),
|
||||
text: t(
|
||||
'explore.query-library.delete-query-text',
|
||||
"You're about to remove this query from the query library. This action cannot be undone. Do you want to continue?"
|
||||
),
|
||||
yesText: t('query-library.delete-query-button', 'Delete query'),
|
||||
icon: 'trash-alt',
|
||||
onConfirm: () => performDelete(queryUid),
|
||||
})
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<div className={styles.cell}>
|
||||
<IconButton
|
||||
className={styles.actionButton}
|
||||
size="lg"
|
||||
name="trash-alt"
|
||||
title={t('explore.query-library.delete-query', 'Delete query')}
|
||||
tooltip={t('explore.query-library.delete-query', 'Delete query')}
|
||||
onClick={() => {
|
||||
if (queryUid) {
|
||||
onDeleteQuery(queryUid);
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<ExploreRunQueryButton queries={query ? [query] : []} rootDatasourceUid={rootDatasourceUid} />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export default ActionsCell;
|
||||
|
@ -26,7 +26,7 @@ const columns: Array<Column<QueryTemplateRow>> = [
|
||||
id: 'actions',
|
||||
header: '',
|
||||
cell: ({ row: { original } }) => (
|
||||
<ActionsCell query={original.query} rootDatasourceUid={original.datasourceRef?.uid} />
|
||||
<ActionsCell query={original.query} rootDatasourceUid={original.datasourceRef?.uid} queryUid={original.uid} />
|
||||
),
|
||||
},
|
||||
];
|
||||
|
@ -34,4 +34,14 @@ const getStyles = (theme: GrafanaTheme2) => ({
|
||||
WebkitLineClamp: 1,
|
||||
overflow: 'hidden',
|
||||
}),
|
||||
cell: css({
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
'&:last-child': {
|
||||
justifyContent: 'end',
|
||||
},
|
||||
}),
|
||||
actionButton: css({
|
||||
padding: theme.spacing(1),
|
||||
}),
|
||||
});
|
||||
|
@ -7,4 +7,5 @@ export type QueryTemplateRow = {
|
||||
datasourceRef?: DataSourceRef | null;
|
||||
datasourceType?: string;
|
||||
createdAtTimestamp?: number;
|
||||
uid?: string;
|
||||
};
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { createApi } from '@reduxjs/toolkit/query/react';
|
||||
|
||||
import { AddQueryTemplateCommand, QueryTemplate } from '../types';
|
||||
import { AddQueryTemplateCommand, DeleteQueryTemplateCommand, QueryTemplate } from '../types';
|
||||
|
||||
import { convertAddQueryTemplateCommandToDataQuerySpec, convertDataQueryResponseToQueryTemplates } from './mappers';
|
||||
import { baseQuery } from './query';
|
||||
@ -21,6 +21,13 @@ export const queryLibraryApi = createApi({
|
||||
}),
|
||||
invalidatesTags: ['QueryTemplatesList'],
|
||||
}),
|
||||
deleteQueryTemplate: builder.mutation<void, DeleteQueryTemplateCommand>({
|
||||
query: ({ uid }) => ({
|
||||
url: `${uid}`,
|
||||
method: 'DELETE',
|
||||
}),
|
||||
invalidatesTags: ['QueryTemplatesList'],
|
||||
}),
|
||||
}),
|
||||
reducerPath: 'queryLibrary',
|
||||
});
|
||||
|
@ -24,15 +24,20 @@ export enum QueryTemplateKinds {
|
||||
*/
|
||||
export const BASE_URL = `/apis/${API_VERSION}/namespaces/default/querytemplates/`;
|
||||
|
||||
// URL is optional for these requests
|
||||
interface QueryLibraryBackendRequest extends Pick<BackendSrvRequest, 'data' | 'method'> {
|
||||
url?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: similar code is duplicated in many places. To be unified in #86960
|
||||
*/
|
||||
export const baseQuery: BaseQueryFn<Pick<BackendSrvRequest, 'data' | 'method'>, DataQuerySpecResponse, Error> = async (
|
||||
export const baseQuery: BaseQueryFn<QueryLibraryBackendRequest, DataQuerySpecResponse, Error> = async (
|
||||
requestOptions
|
||||
) => {
|
||||
try {
|
||||
const responseObservable = getBackendSrv().fetch<DataQuerySpecResponse>({
|
||||
url: BASE_URL,
|
||||
url: `${BASE_URL}${requestOptions.url ?? ''}`,
|
||||
showErrorAlert: true,
|
||||
method: requestOptions.method || 'GET',
|
||||
data: requestOptions.data,
|
||||
|
@ -12,7 +12,8 @@ import { config } from '@grafana/runtime';
|
||||
import { queryLibraryApi } from './api/factory';
|
||||
import { mockData } from './api/mocks';
|
||||
|
||||
export const { useAllQueryTemplatesQuery, useAddQueryTemplateMutation } = queryLibraryApi;
|
||||
export const { useAllQueryTemplatesQuery, useAddQueryTemplateMutation, useDeleteQueryTemplateMutation } =
|
||||
queryLibraryApi;
|
||||
|
||||
export function isQueryLibraryEnabled() {
|
||||
return config.featureToggles.queryLibrary;
|
||||
|
@ -11,3 +11,7 @@ export type AddQueryTemplateCommand = {
|
||||
title: string;
|
||||
targets: DataQuery[];
|
||||
};
|
||||
|
||||
export type DeleteQueryTemplateCommand = {
|
||||
uid: string;
|
||||
};
|
||||
|
@ -532,6 +532,12 @@
|
||||
"scan-for-older-logs": "Scan for older logs",
|
||||
"stop-scan": "Stop scan"
|
||||
},
|
||||
"query-library": {
|
||||
"delete-query": "Delete query",
|
||||
"delete-query-text": "You're about to remove this query from the query library. This action cannot be undone. Do you want to continue?",
|
||||
"delete-query-title": "Delete query",
|
||||
"query-deleted": "Query deleted"
|
||||
},
|
||||
"rich-history": {
|
||||
"close-tooltip": "Close query history",
|
||||
"datasource-a-z": "Data source A-Z",
|
||||
@ -1559,6 +1565,9 @@
|
||||
"role-label": "Role"
|
||||
}
|
||||
},
|
||||
"query-library": {
|
||||
"delete-query-button": "Delete query"
|
||||
},
|
||||
"query-operation": {
|
||||
"header": {
|
||||
"collapse-row": "Collapse query row",
|
||||
|
@ -532,6 +532,12 @@
|
||||
"scan-for-older-logs": "Ŝčäʼn ƒőř őľđęř ľőģş",
|
||||
"stop-scan": "Ŝŧőp şčäʼn"
|
||||
},
|
||||
"query-library": {
|
||||
"delete-query": "Đęľęŧę qūęřy",
|
||||
"delete-query-text": "Ÿőū'řę äþőūŧ ŧő řęmővę ŧĥįş qūęřy ƒřőm ŧĥę qūęřy ľįþřäřy. Ŧĥįş äčŧįőʼn čäʼnʼnőŧ þę ūʼnđőʼnę. Đő yőū ŵäʼnŧ ŧő čőʼnŧįʼnūę?",
|
||||
"delete-query-title": "Đęľęŧę qūęřy",
|
||||
"query-deleted": "Qūęřy đęľęŧęđ"
|
||||
},
|
||||
"rich-history": {
|
||||
"close-tooltip": "Cľőşę qūęřy ĥįşŧőřy",
|
||||
"datasource-a-z": "Đäŧä şőūřčę Å-Ż",
|
||||
@ -1559,6 +1565,9 @@
|
||||
"role-label": "Ŗőľę"
|
||||
}
|
||||
},
|
||||
"query-library": {
|
||||
"delete-query-button": "Đęľęŧę qūęřy"
|
||||
},
|
||||
"query-operation": {
|
||||
"header": {
|
||||
"collapse-row": "Cőľľäpşę qūęřy řőŵ",
|
||||
|
Loading…
Reference in New Issue
Block a user