Alerting: Use Unstructured type for settings of K8s model Integration + code owners (#91430)

* remove nonnamespaced paths
* use common.Unstructed for Intergration.Settings
* update codeowners to include alerting
* fix json name of secure fields to start with lower case
This commit is contained in:
Yuri Tseretyan 2024-08-02 13:02:58 -04:00 committed by GitHub
parent 1747cd1747
commit 96f7f0f486
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 57 additions and 40 deletions

3
.github/CODEOWNERS vendored
View File

@ -74,6 +74,7 @@
/apps/alerting/ @grafana/alerting-backend /apps/alerting/ @grafana/alerting-backend
/pkg/api/ @grafana/grafana-backend-group /pkg/api/ @grafana/grafana-backend-group
/pkg/apis/ @grafana/grafana-app-platform-squad /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/bus/ @grafana/grafana-search-and-storage
/pkg/cmd/ @grafana/grafana-backend-group /pkg/cmd/ @grafana/grafana-backend-group
/pkg/cmd/grafana/apiserver @grafana/grafana-app-platform-squad /pkg/cmd/grafana/apiserver @grafana/grafana-app-platform-squad
@ -150,6 +151,7 @@
/pkg/setting/ @grafana/grafana-backend-services-squad /pkg/setting/ @grafana/grafana-backend-services-squad
/pkg/tests/ @grafana/grafana-backend-services-squad /pkg/tests/ @grafana/grafana-backend-services-squad
/pkg/tests/apis/ @grafana/grafana-app-platform-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/tests/api/correlations/ @grafana/explore-squad
/pkg/tsdb/grafanads/ @grafana/grafana-backend-group /pkg/tsdb/grafanads/ @grafana/grafana-backend-group
/pkg/tsdb/opentsdb/ @grafana/partner-datasources /pkg/tsdb/opentsdb/ @grafana/partner-datasources
@ -651,6 +653,7 @@ embed.go @grafana/grafana-as-code
/pkg/kinds/ @grafana/grafana-as-code /pkg/kinds/ @grafana/grafana-as-code
/pkg/registry/ @grafana/grafana-as-code /pkg/registry/ @grafana/grafana-as-code
/pkg/registry/apis/ @grafana/grafana-app-platform-squad /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/ @grafana/grafana-as-code
/pkg/codegen/generators @grafana/grafana-as-code /pkg/codegen/generators @grafana/grafana-as-code
/pkg/kinds/*/*_gen.go @grafana/grafana-as-code /pkg/kinds/*/*_gen.go @grafana/grafana-as-code

View File

@ -1,15 +1,16 @@
package v0alpha1 package v0alpha1
import "encoding/json" import (
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
)
// Integration defines model for Integration. // Integration defines model for Integration.
// +k8s:openapi-gen=true // +k8s:openapi-gen=true
type Integration struct { type Integration struct {
DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"` DisableResolveMessage *bool `json:"disableResolveMessage,omitempty"`
// +mapType=atomic // +mapType=atomic
SecureFields map[string]bool `json:"SecureFields,omitempty"` SecureFields map[string]bool `json:"secureFields,omitempty"`
// +listType=atomic Settings common.Unstructured `json:"settings"`
Settings json.RawMessage `json:"settings"`
Type string `json:"type"` Type string `json:"type"`
Uid *string `json:"uid,omitempty"` Uid *string `json:"uid,omitempty"`
} }

View File

@ -8,8 +8,6 @@
package v0alpha1 package v0alpha1
import ( import (
json "encoding/json"
runtime "k8s.io/apimachinery/pkg/runtime" runtime "k8s.io/apimachinery/pkg/runtime"
) )
@ -28,11 +26,7 @@ func (in *Integration) DeepCopyInto(out *Integration) {
(*out)[key] = val (*out)[key] = val
} }
} }
if in.Settings != nil { in.Settings.DeepCopyInto(&out.Settings)
in, out := &in.Settings, &out.Settings
*out = make(json.RawMessage, len(*in))
copy(*out, *in)
}
if in.Uid != nil { if in.Uid != nil {
in, out := &in.Uid, &out.Uid in, out := &in.Uid, &out.Uid
*out = new(string) *out = new(string)

View File

@ -48,28 +48,12 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_Integration(ref common.Refe
}, },
}, },
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{"object"}, Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
AdditionalProperties: &spec.SchemaOrBool{
Allows: true,
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Default: false,
Type: []string{"boolean"},
Format: "",
},
},
},
}, },
}, },
"settings": { "settings": {
VendorExtensible: spec.VendorExtensible{
Extensions: spec.Extensions{
"x-kubernetes-list-type": "atomic",
},
},
SchemaProps: spec.SchemaProps{ SchemaProps: spec.SchemaProps{
Type: []string{"string"}, Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
Format: "byte",
}, },
}, },
"type": { "type": {
@ -89,6 +73,8 @@ func schema_pkg_apis_alerting_notifications_v0alpha1_Integration(ref common.Refe
Required: []string{"settings", "type"}, Required: []string{"settings", "type"},
}, },
}, },
Dependencies: []string{
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
} }
} }

View File

@ -8,6 +8,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/types" "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" 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/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "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") return nil, fmt.Errorf("all integrations must have the same provenance")
} }
provenance = integration.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{ spec.Integrations = append(spec.Integrations, model.Integration{
Uid: &integration.UID, Uid: &integration.UID,
Type: integration.Type, Type: integration.Type,
DisableResolveMessage: &integration.DisableResolveMessage, DisableResolveMessage: &integration.DisableResolveMessage,
Settings: json.RawMessage(integration.Settings), Settings: unstruct,
SecureFields: integration.SecureFields, SecureFields: integration.SecureFields,
}) })
} }
@ -79,12 +85,16 @@ func convertToDomainModel(receiver *model.Receiver) (definitions.GettableApiRece
} }
for _, integration := range receiver.Spec.Integrations { 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{ grafanaIntegration := definitions.GettableGrafanaReceiver{
Name: receiver.Spec.Title, Name: receiver.Spec.Title,
Type: integration.Type, Type: integration.Type,
Settings: definitions.RawMessage(integration.Settings), Settings: definitions.RawMessage(data),
SecureFields: integration.SecureFields, SecureFields: integration.SecureFields,
//Provenance: "", //TODO: Convert provenance? Provenance: definitions.Provenance(models.ProvenanceNone),
} }
if integration.Uid != nil { if integration.Uid != nil {
grafanaIntegration.UID = *integration.Uid grafanaIntegration.UID = *integration.Uid

View File

@ -13,6 +13,7 @@ import (
"k8s.io/apiserver/pkg/registry/rest" "k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server" genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/kube-openapi/pkg/common" "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
notificationsModels "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1" notificationsModels "github.com/grafana/grafana/pkg/apis/alerting_notifications/v0alpha1"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest" grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
@ -55,11 +56,11 @@ func RegisterAPIService(
return builder return builder
} }
func (t NotificationsAPIBuilder) GetGroupVersion() schema.GroupVersion { func (t *NotificationsAPIBuilder) GetGroupVersion() schema.GroupVersion {
return t.gv return t.gv
} }
func (t NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error { func (t *NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
err := notificationsModels.AddToScheme(scheme) err := notificationsModels.AddToScheme(scheme)
if err != nil { if err != nil {
return err return err
@ -67,7 +68,7 @@ func (t NotificationsAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
return scheme.SetVersionPriority(notificationsModels.SchemeGroupVersion) return scheme.SetVersionPriority(notificationsModels.SchemeGroupVersion)
} }
func (t NotificationsAPIBuilder) GetAPIGroupInfo( func (t *NotificationsAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme, scheme *runtime.Scheme,
codecs serializer.CodecFactory, codecs serializer.CodecFactory,
optsGetter generic.RESTOptionsGetter, optsGetter generic.RESTOptionsGetter,
@ -92,15 +93,35 @@ func (t NotificationsAPIBuilder) GetAPIGroupInfo(
return &apiGroupInfo, nil return &apiGroupInfo, nil
} }
func (t NotificationsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions { func (t *NotificationsAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
return notificationsModels.GetOpenAPIDefinitions return notificationsModels.GetOpenAPIDefinitions
} }
func (t NotificationsAPIBuilder) GetAPIRoutes() *builder.APIRoutes { func (t *NotificationsAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
return nil 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( return authorizer.AuthorizerFunc(
func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) { func(ctx context.Context, a authorizer.Attributes) (authorizer.Decision, string, error) {
switch a.GetResource() { switch a.GetResource() {

View File

@ -10,6 +10,7 @@ import (
model "github.com/grafana/grafana/pkg/apis/alerting_notifications/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/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/ngalert/api/tooling/definitions" "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) { 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.Version = interval.ResourceVersion
result.UID = interval.ObjectMeta.Name result.UID = interval.ObjectMeta.Name
result.Provenance = definitions.Provenance(models.ProvenanceNone)
err = result.Validate() err = result.Validate()
if err != nil { if err != nil {
return definitions.MuteTimeInterval{}, err return definitions.MuteTimeInterval{}, err