From b07592620279f16b0353444e7aba3c457c50d7ec Mon Sep 17 00:00:00 2001 From: Yuri Tseretyan Date: Thu, 20 Jun 2024 16:52:03 -0400 Subject: [PATCH] Alerting: Time Intervals API (#88201) * expose ngalert API to public * add delete action to time-intervals * introduce time-interval model generated by app-platform-sdk from CUE model the fields of the model are chosen to be compatible with the current model * implement api server * add feature flag alertingApiServer ---- Test Infra * update helper to support creating custom users with enterprise permissions * add generator for Interval model --- .github/CODEOWNERS | 1 + .../alerting/notifications/cue.mod/module.cue | 1 + apps/alerting/notifications/timeInterval.cue | 37 ++ .../feature-toggles/index.md | 1 + hack/README.md | 9 +- .../src/types/featureToggles.gen.ts | 1 + .../alerting_notifications/v0alpha1/doc.go | 6 + .../v0alpha1/register.go | 53 ++ .../v0alpha1/testing.go | 115 ++++ .../v0alpha1/timeinterval_spec.go | 38 ++ .../alerting_notifications/v0alpha1/types.go | 87 +++ .../v0alpha1/zz_generated.deepcopy.go | 157 +++++ .../v0alpha1/zz_generated.defaults.go | 19 + .../v0alpha1/zz_generated.openapi.go | 303 ++++++++++ ...enerated.openapi_violation_exceptions.list | 4 + .../v0alpha1/interval.go | 83 +++ .../v0alpha1/timeinterval.go | 196 +++++++ .../v0alpha1/timeintervalspec.go | 39 ++ .../v0alpha1/timerange.go | 34 ++ pkg/generated/applyconfiguration/utils.go | 26 +- .../clientset/versioned/clientset.go | 15 +- .../versioned/fake/clientset_generated.go | 7 + .../clientset/versioned/fake/register.go | 2 + .../clientset/versioned/scheme/register.go | 2 + .../v0alpha1/alerting_notifications_client.go | 93 +++ .../alerting_notifications/v0alpha1/doc.go | 6 + .../v0alpha1/fake/doc.go | 6 + .../fake_alerting_notifications_client.go | 26 + .../v0alpha1/fake/fake_timeinterval.go | 140 +++++ .../v0alpha1/generated_expansion.go | 7 + .../v0alpha1/timeinterval.go | 194 +++++++ .../alerting_notifications/interface.go | 32 ++ .../v0alpha1/interface.go | 31 + .../v0alpha1/timeinterval.go | 76 +++ .../informers/externalversions/factory.go | 6 + .../informers/externalversions/generic.go | 11 +- .../v0alpha1/expansion_generated.go | 13 + .../v0alpha1/timeinterval.go | 85 +++ .../apis/alerting/notifications/register.go | 112 ++++ .../notifications/timeinterval/authorize.go | 54 ++ .../notifications/timeinterval/conversions.go | 97 ++++ .../timeinterval/legacy_storage.go | 205 +++++++ .../notifications/timeinterval/storage.go | 79 +++ pkg/registry/apis/apis.go | 2 + pkg/registry/apis/wireset.go | 2 + pkg/services/accesscontrol/models.go | 5 +- pkg/services/featuremgmt/registry.go | 7 + pkg/services/featuremgmt/toggles_gen.csv | 1 + pkg/services/featuremgmt/toggles_gen.go | 4 + pkg/services/featuremgmt/toggles_gen.json | 15 +- pkg/services/ngalert/ngalert.go | 8 +- .../timeinterval/timeinterval_test.go | 541 ++++++++++++++++++ pkg/tests/apis/helper.go | 111 ++-- 53 files changed, 3150 insertions(+), 55 deletions(-) create mode 100644 apps/alerting/notifications/cue.mod/module.cue create mode 100644 apps/alerting/notifications/timeInterval.cue create mode 100644 pkg/apis/alerting_notifications/v0alpha1/doc.go create mode 100644 pkg/apis/alerting_notifications/v0alpha1/register.go create mode 100644 pkg/apis/alerting_notifications/v0alpha1/testing.go create mode 100644 pkg/apis/alerting_notifications/v0alpha1/timeinterval_spec.go create mode 100644 pkg/apis/alerting_notifications/v0alpha1/types.go create mode 100644 pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go create mode 100644 pkg/apis/alerting_notifications/v0alpha1/zz_generated.defaults.go create mode 100644 pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go create mode 100644 pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi_violation_exceptions.list create mode 100644 pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/interval.go create mode 100644 pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timeinterval.go create mode 100644 pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timeintervalspec.go create mode 100644 pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timerange.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/alerting_notifications_client.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/doc.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/doc.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_alerting_notifications_client.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_timeinterval.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/generated_expansion.go create mode 100644 pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/timeinterval.go create mode 100644 pkg/generated/informers/externalversions/alerting_notifications/interface.go create mode 100644 pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/interface.go create mode 100644 pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/timeinterval.go create mode 100644 pkg/generated/listers/alerting_notifications/v0alpha1/expansion_generated.go create mode 100644 pkg/generated/listers/alerting_notifications/v0alpha1/timeinterval.go create mode 100644 pkg/registry/apis/alerting/notifications/register.go create mode 100644 pkg/registry/apis/alerting/notifications/timeinterval/authorize.go create mode 100644 pkg/registry/apis/alerting/notifications/timeinterval/conversions.go create mode 100644 pkg/registry/apis/alerting/notifications/timeinterval/legacy_storage.go create mode 100644 pkg/registry/apis/alerting/notifications/timeinterval/storage.go create mode 100644 pkg/tests/apis/alerting/notifications/timeinterval/timeinterval_test.go diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index a2fc88371ba..cf7bc7537ad 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -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 diff --git a/apps/alerting/notifications/cue.mod/module.cue b/apps/alerting/notifications/cue.mod/module.cue new file mode 100644 index 00000000000..d5f6f45c25e --- /dev/null +++ b/apps/alerting/notifications/cue.mod/module.cue @@ -0,0 +1 @@ +module:"notifications" \ No newline at end of file diff --git a/apps/alerting/notifications/timeInterval.cue b/apps/alerting/notifications/timeInterval.cue new file mode 100644 index 00000000000..04bff2c995e --- /dev/null +++ b/apps/alerting/notifications/timeInterval.cue @@ -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] + } + } + } + } +} \ No newline at end of file diff --git a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md index 5f6237ff724..567d4a087a0 100644 --- a/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md +++ b/docs/sources/setup-grafana/configure-grafana/feature-toggles/index.md @@ -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 diff --git a/hack/README.md b/hack/README.md index 5a8819a9db0..f425f5500d5 100644 --- a/hack/README.md +++ b/hack/README.md @@ -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. `/v` (e.g. service/v0alpha1) +- the resource directory contains file `types.go` that includes resource definitions +- the resource definitions are annotated with comment `// +genclient` diff --git a/packages/grafana-data/src/types/featureToggles.gen.ts b/packages/grafana-data/src/types/featureToggles.gen.ts index 91d4bcf2a65..12affb3258d 100644 --- a/packages/grafana-data/src/types/featureToggles.gen.ts +++ b/packages/grafana-data/src/types/featureToggles.gen.ts @@ -198,4 +198,5 @@ export interface FeatureToggles { databaseReadReplica?: boolean; zanzana?: boolean; passScopeToDashboardApi?: boolean; + alertingApiServer?: boolean; } diff --git a/pkg/apis/alerting_notifications/v0alpha1/doc.go b/pkg/apis/alerting_notifications/v0alpha1/doc.go new file mode 100644 index 00000000000..f1e30a11e31 --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/doc.go @@ -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" diff --git a/pkg/apis/alerting_notifications/v0alpha1/register.go b/pkg/apis/alerting_notifications/v0alpha1/register.go new file mode 100644 index 00000000000..a6be83e04ef --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/register.go @@ -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() +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/testing.go b/pkg/apis/alerting_notifications/v0alpha1/testing.go new file mode 100644 index 00000000000..355f7422f20 --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/testing.go @@ -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 +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/timeinterval_spec.go b/pkg/apis/alerting_notifications/v0alpha1/timeinterval_spec.go new file mode 100644 index 00000000000..f345bda7416 --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/timeinterval_spec.go @@ -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"` +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/types.go b/pkg/apis/alerting_notifications/v0alpha1/types.go new file mode 100644 index 00000000000..6a970ad4516 --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/types.go @@ -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"` +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go new file mode 100644 index 00000000000..918c3d81cb2 --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go @@ -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 +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.defaults.go b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.defaults.go new file mode 100644 index 00000000000..238fc2f4edc --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.defaults.go @@ -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 +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go new file mode 100644 index 00000000000..8d5ff04f0a0 --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go @@ -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"}, + }, + }, + } +} diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi_violation_exceptions.list b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi_violation_exceptions.list new file mode 100644 index 00000000000..7b756e64dfd --- /dev/null +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi_violation_exceptions.list @@ -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 diff --git a/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/interval.go b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/interval.go new file mode 100644 index 00000000000..951000054bb --- /dev/null +++ b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/interval.go @@ -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 +} diff --git a/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timeinterval.go b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timeinterval.go new file mode 100644 index 00000000000..f94c6e8f95f --- /dev/null +++ b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timeinterval.go @@ -0,0 +1,196 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by applyconfiguration-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + v1 "k8s.io/client-go/applyconfigurations/meta/v1" +) + +// 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 +} diff --git a/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timeintervalspec.go b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timeintervalspec.go new file mode 100644 index 00000000000..ba3b255ed33 --- /dev/null +++ b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timeintervalspec.go @@ -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 +} diff --git a/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timerange.go b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timerange.go new file mode 100644 index 00000000000..77242ddc317 --- /dev/null +++ b/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1/timerange.go @@ -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 +} diff --git a/pkg/generated/applyconfiguration/utils.go b/pkg/generated/applyconfiguration/utils.go index 93ca019455c..561bf3385e5 100644 --- a/pkg/generated/applyconfiguration/utils.go +++ b/pkg/generated/applyconfiguration/utils.go @@ -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 diff --git a/pkg/generated/clientset/versioned/clientset.go b/pkg/generated/clientset/versioned/clientset.go index 55fa581701a..dc02a208e84 100644 --- a/pkg/generated/clientset/versioned/clientset.go +++ b/pkg/generated/clientset/versioned/clientset.go @@ -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) diff --git a/pkg/generated/clientset/versioned/fake/clientset_generated.go b/pkg/generated/clientset/versioned/fake/clientset_generated.go index c7e19bcc918..5b7771eb90a 100644 --- a/pkg/generated/clientset/versioned/fake/clientset_generated.go +++ b/pkg/generated/clientset/versioned/fake/clientset_generated.go @@ -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} diff --git a/pkg/generated/clientset/versioned/fake/register.go b/pkg/generated/clientset/versioned/fake/register.go index bbf12658e76..00ae96dd36e 100644 --- a/pkg/generated/clientset/versioned/fake/register.go +++ b/pkg/generated/clientset/versioned/fake/register.go @@ -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, } diff --git a/pkg/generated/clientset/versioned/scheme/register.go b/pkg/generated/clientset/versioned/scheme/register.go index dd6e9619160..6ae4e0deec1 100644 --- a/pkg/generated/clientset/versioned/scheme/register.go +++ b/pkg/generated/clientset/versioned/scheme/register.go @@ -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, } diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/alerting_notifications_client.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/alerting_notifications_client.go new file mode 100644 index 00000000000..5c81a66d6ec --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/alerting_notifications_client.go @@ -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 +} diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/doc.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/doc.go new file mode 100644 index 00000000000..1c86744fecc --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/doc.go @@ -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 diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/doc.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/doc.go new file mode 100644 index 00000000000..d96b985b3ea --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/doc.go @@ -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 diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_alerting_notifications_client.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_alerting_notifications_client.go new file mode 100644 index 00000000000..ece678250d8 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_alerting_notifications_client.go @@ -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 +} diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_timeinterval.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_timeinterval.go new file mode 100644 index 00000000000..569c83a433d --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/fake/fake_timeinterval.go @@ -0,0 +1,140 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by client-gen. DO NOT EDIT. + +package fake + +import ( + "context" + json "encoding/json" + "fmt" + + v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" + alertingnotificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + labels "k8s.io/apimachinery/pkg/labels" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + testing "k8s.io/client-go/testing" +) + +// 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 +} diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/generated_expansion.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/generated_expansion.go new file mode 100644 index 00000000000..cf6e218f4ae --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/generated_expansion.go @@ -0,0 +1,7 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by client-gen. DO NOT EDIT. + +package v0alpha1 + +type TimeIntervalExpansion interface{} diff --git a/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/timeinterval.go b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/timeinterval.go new file mode 100644 index 00000000000..cc3b2c05bd1 --- /dev/null +++ b/pkg/generated/clientset/versioned/typed/alerting_notifications/v0alpha1/timeinterval.go @@ -0,0 +1,194 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by client-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + "context" + json "encoding/json" + "fmt" + "time" + + v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" + alertingnotificationsv0alpha1 "github.com/grafana/grafana/pkg/generated/applyconfiguration/alerting_notifications/v0alpha1" + scheme "github.com/grafana/grafana/pkg/generated/clientset/versioned/scheme" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + types "k8s.io/apimachinery/pkg/types" + watch "k8s.io/apimachinery/pkg/watch" + rest "k8s.io/client-go/rest" +) + +// 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 +} diff --git a/pkg/generated/informers/externalversions/alerting_notifications/interface.go b/pkg/generated/informers/externalversions/alerting_notifications/interface.go new file mode 100644 index 00000000000..631d480cd39 --- /dev/null +++ b/pkg/generated/informers/externalversions/alerting_notifications/interface.go @@ -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) +} diff --git a/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/interface.go b/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/interface.go new file mode 100644 index 00000000000..2095a04954a --- /dev/null +++ b/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/interface.go @@ -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} +} diff --git a/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/timeinterval.go b/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/timeinterval.go new file mode 100644 index 00000000000..903bb8eb7bc --- /dev/null +++ b/pkg/generated/informers/externalversions/alerting_notifications/v0alpha1/timeinterval.go @@ -0,0 +1,76 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by informer-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + "context" + time "time" + + alertingnotificationsv0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" + versioned "github.com/grafana/grafana/pkg/generated/clientset/versioned" + internalinterfaces "github.com/grafana/grafana/pkg/generated/informers/externalversions/internalinterfaces" + v0alpha1 "github.com/grafana/grafana/pkg/generated/listers/alerting_notifications/v0alpha1" + v1 "k8s.io/apimachinery/pkg/apis/meta/v1" + runtime "k8s.io/apimachinery/pkg/runtime" + watch "k8s.io/apimachinery/pkg/watch" + cache "k8s.io/client-go/tools/cache" +) + +// 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()) +} diff --git a/pkg/generated/informers/externalversions/factory.go b/pkg/generated/informers/externalversions/factory.go index faca5c61edb..1c28112c022 100644 --- a/pkg/generated/informers/externalversions/factory.go +++ b/pkg/generated/informers/externalversions/factory.go @@ -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) } diff --git a/pkg/generated/informers/externalversions/generic.go b/pkg/generated/informers/externalversions/generic.go index 88cc30aa0f9..137fbb60117 100644 --- a/pkg/generated/informers/externalversions/generic.go +++ b/pkg/generated/informers/externalversions/generic.go @@ -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 } diff --git a/pkg/generated/listers/alerting_notifications/v0alpha1/expansion_generated.go b/pkg/generated/listers/alerting_notifications/v0alpha1/expansion_generated.go new file mode 100644 index 00000000000..65bad473129 --- /dev/null +++ b/pkg/generated/listers/alerting_notifications/v0alpha1/expansion_generated.go @@ -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{} diff --git a/pkg/generated/listers/alerting_notifications/v0alpha1/timeinterval.go b/pkg/generated/listers/alerting_notifications/v0alpha1/timeinterval.go new file mode 100644 index 00000000000..225a64b84e5 --- /dev/null +++ b/pkg/generated/listers/alerting_notifications/v0alpha1/timeinterval.go @@ -0,0 +1,85 @@ +// SPDX-License-Identifier: AGPL-3.0-only + +// Code generated by lister-gen. DO NOT EDIT. + +package v0alpha1 + +import ( + v0alpha1 "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" + "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/client-go/tools/cache" +) + +// 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 +} diff --git a/pkg/registry/apis/alerting/notifications/register.go b/pkg/registry/apis/alerting/notifications/register.go new file mode 100644 index 00000000000..cb09efaa456 --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/register.go @@ -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 + }) +} diff --git a/pkg/registry/apis/alerting/notifications/timeinterval/authorize.go b/pkg/registry/apis/alerting/notifications/timeinterval/authorize.go new file mode 100644 index 00000000000..b5ccd9d9812 --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/timeinterval/authorize.go @@ -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 +} diff --git a/pkg/registry/apis/alerting/notifications/timeinterval/conversions.go b/pkg/registry/apis/alerting/notifications/timeinterval/conversions.go new file mode 100644 index 00000000000..cacb6f4369a --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/timeinterval/conversions.go @@ -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 +} diff --git a/pkg/registry/apis/alerting/notifications/timeinterval/legacy_storage.go b/pkg/registry/apis/alerting/notifications/timeinterval/legacy_storage.go new file mode 100644 index 00000000000..64d95e17a12 --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/timeinterval/legacy_storage.go @@ -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") +} diff --git a/pkg/registry/apis/alerting/notifications/timeinterval/storage.go b/pkg/registry/apis/alerting/notifications/timeinterval/storage.go new file mode 100644 index 00000000000..6b627c0c5e5 --- /dev/null +++ b/pkg/registry/apis/alerting/notifications/timeinterval/storage.go @@ -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 +} diff --git a/pkg/registry/apis/apis.go b/pkg/registry/apis/apis.go index 90b7de08755..3ccfae82528 100644 --- a/pkg/registry/apis/apis.go +++ b/pkg/registry/apis/apis.go @@ -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{} } diff --git a/pkg/registry/apis/wireset.go b/pkg/registry/apis/wireset.go index e2bd94e9e7c..d9782c44697 100644 --- a/pkg/registry/apis/wireset.go +++ b/pkg/registry/apis/wireset.go @@ -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, ) diff --git a/pkg/services/accesscontrol/models.go b/pkg/services/accesscontrol/models.go index 8076ef98fe6..7e68c0d8091 100644 --- a/pkg/services/accesscontrol/models.go +++ b/pkg/services/accesscontrol/models.go @@ -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" diff --git a/pkg/services/featuremgmt/registry.go b/pkg/services/featuremgmt/registry.go index 2c2cdbd1763..cc52ce2bcbe 100644 --- a/pkg/services/featuremgmt/registry.go +++ b/pkg/services/featuremgmt/registry.go @@ -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, + }, } ) diff --git a/pkg/services/featuremgmt/toggles_gen.csv b/pkg/services/featuremgmt/toggles_gen.csv index d3552651a93..f286f4f5b22 100644 --- a/pkg/services/featuremgmt/toggles_gen.csv +++ b/pkg/services/featuremgmt/toggles_gen.csv @@ -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 diff --git a/pkg/services/featuremgmt/toggles_gen.go b/pkg/services/featuremgmt/toggles_gen.go index 2ddff1bb681..41267fbccdc 100644 --- a/pkg/services/featuremgmt/toggles_gen.go +++ b/pkg/services/featuremgmt/toggles_gen.go @@ -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" ) diff --git a/pkg/services/featuremgmt/toggles_gen.json b/pkg/services/featuremgmt/toggles_gen.json index 1535a47673d..a2338880416 100644 --- a/pkg/services/featuremgmt/toggles_gen.json +++ b/pkg/services/featuremgmt/toggles_gen.json @@ -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 @@ } } ] -} +} \ No newline at end of file diff --git a/pkg/services/ngalert/ngalert.go b/pkg/services/ngalert/ngalert.go index 952e1d86814..09819ac4e08 100644 --- a/pkg/services/ngalert/ngalert.go +++ b/pkg/services/ngalert/ngalert.go @@ -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 @@ -409,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, @@ -438,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 @@ -516,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 { diff --git a/pkg/tests/apis/alerting/notifications/timeinterval/timeinterval_test.go b/pkg/tests/apis/alerting/notifications/timeinterval/timeinterval_test.go new file mode 100644 index 00000000000..c8141cbcdee --- /dev/null +++ b/pkg/tests/apis/alerting/notifications/timeinterval/timeinterval_test.go @@ -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 + }) +} diff --git a/pkg/tests/apis/helper.go b/pkg/tests/apis/helper.go index c1e1529b2b6..5fa3c0633cb 100644 --- a/pkg/tests/apis/helper.go +++ b/pkg/tests/apis/helper.go @@ -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 - return User{ - Identity: s, - password: key, - baseURL: baseUrl, - } + 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) + + 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) } }