diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 522ebc6e9de..81bdd513f05 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -74,6 +74,7 @@ /apps/alerting/ @grafana/alerting-backend /pkg/api/ @grafana/grafana-backend-group /pkg/apis/ @grafana/grafana-app-platform-squad +/pkg/apis/alerting_notifications @grafana/grafana-app-platform-squad @grafana/alerting-backend @grafana/alerting-frontend /pkg/bus/ @grafana/grafana-search-and-storage /pkg/cmd/ @grafana/grafana-backend-group /pkg/cmd/grafana/apiserver @grafana/grafana-app-platform-squad @@ -150,6 +151,7 @@ /pkg/setting/ @grafana/grafana-backend-services-squad /pkg/tests/ @grafana/grafana-backend-services-squad /pkg/tests/apis/ @grafana/grafana-app-platform-squad +/pkg/tests/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend /pkg/tests/api/correlations/ @grafana/explore-squad /pkg/tsdb/grafanads/ @grafana/grafana-backend-group /pkg/tsdb/opentsdb/ @grafana/partner-datasources @@ -651,6 +653,7 @@ embed.go @grafana/grafana-as-code /pkg/kinds/ @grafana/grafana-as-code /pkg/registry/ @grafana/grafana-as-code /pkg/registry/apis/ @grafana/grafana-app-platform-squad +/pkg/registry/apis/alerting @grafana/grafana-app-platform-squad @grafana/alerting-backend /pkg/codegen/ @grafana/grafana-as-code /pkg/codegen/generators @grafana/grafana-as-code /pkg/kinds/*/*_gen.go @grafana/grafana-as-code diff --git a/pkg/apis/alerting_notifications/v0alpha1/receiver_spec.go b/pkg/apis/alerting_notifications/v0alpha1/receiver_spec.go index 0d1d942fd8d..18298dfa691 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/receiver_spec.go +++ b/pkg/apis/alerting_notifications/v0alpha1/receiver_spec.go @@ -1,17 +1,18 @@ package v0alpha1 -import "encoding/json" +import ( + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" +) // Integration defines model for Integration. // +k8s:openapi-gen=true type Integration struct { DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"` // +mapType=atomic - SecureFields map[string]bool `json:"SecureFields,omitempty"` - // +listType=atomic - Settings json.RawMessage `json:"settings"` - Type string `json:"type"` - Uid *string `json:"uid,omitempty"` + SecureFields map[string]bool `json:"secureFields,omitempty"` + Settings common.Unstructured `json:"settings"` + Type string `json:"type"` + Uid *string `json:"uid,omitempty"` } // ReceiverSpec defines model for Spec. diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go index 47eb7b98351..ca7b6aa8a6d 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.deepcopy.go @@ -8,8 +8,6 @@ package v0alpha1 import ( - json "encoding/json" - runtime "k8s.io/apimachinery/pkg/runtime" ) @@ -28,11 +26,7 @@ func (in *Integration) DeepCopyInto(out *Integration) { (*out)[key] = val } } - if in.Settings != nil { - in, out := &in.Settings, &out.Settings - *out = make(json.RawMessage, len(*in)) - copy(*out, *in) - } + in.Settings.DeepCopyInto(&out.Settings) if in.Uid != nil { in, out := &in.Uid, &out.Uid *out = new(string) diff --git a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go index b0125bb8ca1..afcb58af88b 100644 --- a/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go +++ b/pkg/apis/alerting_notifications/v0alpha1/zz_generated.openapi.go @@ -48,28 +48,12 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_Integration(ref common.Refe }, }, SchemaProps: spec.SchemaProps{ - Type: []string{"object"}, - AdditionalProperties: &spec.SchemaOrBool{ - Allows: true, - Schema: &spec.Schema{ - SchemaProps: spec.SchemaProps{ - Default: false, - Type: []string{"boolean"}, - Format: "", - }, - }, - }, + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), }, }, "settings": { - VendorExtensible: spec.VendorExtensible{ - Extensions: spec.Extensions{ - "x-kubernetes-list-type": "atomic", - }, - }, SchemaProps: spec.SchemaProps{ - Type: []string{"string"}, - Format: "byte", + Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"), }, }, "type": { @@ -89,6 +73,8 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_Integration(ref common.Refe Required: []string{"settings", "type"}, }, }, + Dependencies: []string{ + "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"}, } } diff --git a/pkg/registry/apis/alerting/notifications/receiver/conversions.go b/pkg/registry/apis/alerting/notifications/receiver/conversions.go index 81fe6f96501..9bb8fd15920 100644 --- a/pkg/registry/apis/alerting/notifications/receiver/conversions.go +++ b/pkg/registry/apis/alerting/notifications/receiver/conversions.go @@ -8,6 +8,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/types" + common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1" 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" @@ -43,11 +44,16 @@ func convertToK8sResource(orgID int64, receiver definitions.GettableApiReceiver, return nil, fmt.Errorf("all integrations must have the same provenance") } provenance = integration.Provenance + unstruct := common.Unstructured{} + err := json.Unmarshal(integration.Settings, &unstruct) + if err != nil { + return nil, fmt.Errorf("integration '%s' of receiver '%s' has settings that cannot be parsed as JSON: %w", integration.Type, receiver.Name, err) + } spec.Integrations = append(spec.Integrations, model.Integration{ Uid: &integration.UID, Type: integration.Type, DisableResolveMessage: &integration.DisableResolveMessage, - Settings: json.RawMessage(integration.Settings), + Settings: unstruct, SecureFields: integration.SecureFields, }) } @@ -79,12 +85,16 @@ func convertToDomainModel(receiver *model.Receiver) (definitions.GettableApiRece } for _, integration := range receiver.Spec.Integrations { + data, err := integration.Settings.MarshalJSON() + if err != nil { + return definitions.GettableApiReceiver{}, fmt.Errorf("integration '%s' of receiver '%s' is invalid: failed to convert unstructured data to bytes: %w", integration.Type, receiver.Name, err) + } grafanaIntegration := definitions.GettableGrafanaReceiver{ Name: receiver.Spec.Title, Type: integration.Type, - Settings: definitions.RawMessage(integration.Settings), + Settings: definitions.RawMessage(data), SecureFields: integration.SecureFields, - //Provenance: "", //TODO: Convert provenance? + Provenance: definitions.Provenance(models.ProvenanceNone), } if integration.Uid != nil { grafanaIntegration.UID = *integration.Uid diff --git a/pkg/registry/apis/alerting/notifications/register.go b/pkg/registry/apis/alerting/notifications/register.go index b214cd4cca1..32c007b5a2a 100644 --- a/pkg/registry/apis/alerting/notifications/register.go +++ b/pkg/registry/apis/alerting/notifications/register.go @@ -13,6 +13,7 @@ import ( "k8s.io/apiserver/pkg/registry/rest" genericapiserver "k8s.io/apiserver/pkg/server" "k8s.io/kube-openapi/pkg/common" + "k8s.io/kube-openapi/pkg/spec3" notificationsModels "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" @@ -55,11 +56,11 @@ func RegisterAPIService( return builder } -func (t NotificationsAPIBuilder) GetGroupVersion() schema.GroupVersion { +func (t *NotificationsAPIBuilder) GetGroupVersion() schema.GroupVersion { return t.gv } -func (t NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { +func (t *NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { err := notificationsModels.AddToScheme(scheme) if err != nil { return err @@ -67,7 +68,7 @@ func (t NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { return scheme.SetVersionPriority(notificationsModels.SchemeGroupVersion) } -func (t NotificationsAPIBuilder) GetAPIGroupInfo( +func (t *NotificationsAPIBuilder) GetAPIGroupInfo( scheme *runtime.Scheme, codecs serializer.CodecFactory, optsGetter generic.RESTOptionsGetter, @@ -92,15 +93,35 @@ func (t NotificationsAPIBuilder) GetAPIGroupInfo( return &apiGroupInfo, nil } -func (t NotificationsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { +func (t *NotificationsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { return notificationsModels.GetOpenAPIDefinitions } -func (t NotificationsAPIBuilder) GetAPIRoutes() *builder.APIRoutes { +func (t *NotificationsAPIBuilder) GetAPIRoutes() *builder.APIRoutes { return nil } -func (t NotificationsAPIBuilder) GetAuthorizer() authorizer.Authorizer { +// PostProcessOpenAPI is a hook to alter OpenAPI3 specification of the API server. +func (t *NotificationsAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) { + // The plugin description + oas.Info.Description = "Grafana Alerting Notification resources" + + // The root api URL + root := "/apis/" + t.GetGroupVersion().String() + "/" + + // Hide the ability to list or watch across all tenants + delete(oas.Paths.Paths, root+notificationsModels.ReceiverResourceInfo.GroupResource().Resource) + delete(oas.Paths.Paths, root+notificationsModels.TimeIntervalResourceInfo.GroupResource().Resource) + + // The root API discovery list + sub := oas.Paths.Paths[root] + if sub != nil && sub.Get != nil { + sub.Get.Tags = []string{"API Discovery"} // sorts first in the list + } + return oas, nil +} + +func (t *NotificationsAPIBuilder) GetAuthorizer() authorizer.Authorizer { return authorizer.AuthorizerFunc( func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { switch a.GetResource() { diff --git a/pkg/registry/apis/alerting/notifications/timeinterval/conversions.go b/pkg/registry/apis/alerting/notifications/timeinterval/conversions.go index 93845cb37c7..7b0bc3ab703 100644 --- a/pkg/registry/apis/alerting/notifications/timeinterval/conversions.go +++ b/pkg/registry/apis/alerting/notifications/timeinterval/conversions.go @@ -10,6 +10,7 @@ import ( model "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" "github.com/grafana/grafana/pkg/services/apiserver/endpoints/request" "github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" + "github.com/grafana/grafana/pkg/services/ngalert/models" ) func convertToK8sResources(orgID int64, intervals []definitions.MuteTimeInterval, namespacer request.NamespaceMapper, selector fields.Selector) (*model.TimeIntervalList, error) { @@ -77,6 +78,7 @@ func convertToDomainModel(interval *model.TimeInterval) (definitions.MuteTimeInt } result.Version = interval.ResourceVersion result.UID = interval.ObjectMeta.Name + result.Provenance = definitions.Provenance(models.ProvenanceNone) err = result.Validate() if err != nil { return definitions.MuteTimeInterval{}, err