Files

168 lines
5.6 KiB
Go

package runner
import (
"fmt"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/authorization/authorizer"
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/kube-openapi/pkg/common"
"github.com/grafana/grafana-app-sdk/app"
"github.com/grafana/grafana-app-sdk/resource"
"github.com/grafana/grafana/pkg/apimachinery/utils"
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
)
var _ AppBuilder = (*appBuilder)(nil)
type LegacyStorageGetter func(schema.GroupVersionResource) grafanarest.Storage
type AppBuilderConfig struct {
Authorizer authorizer.Authorizer
LegacyStorageGetter LegacyStorageGetter
OpenAPIDefGetter common.GetOpenAPIDefinitions
ManagedKinds map[schema.GroupVersion][]resource.Kind
CustomConfig any
// Do not set anything here unless you have special circumstances! This is a list of resources that are allowed to be accessed in v0alpha1,
// to prevent accidental exposure of experimental APIs. While developing, use the feature flag `grafanaAPIServerWithExperimentalAPIs`.
// And then, when you're ready to expose this to the end user, go to v1beta1 instead.
AllowedV0Alpha1Resources []string
groupVersion schema.GroupVersion
}
type AppBuilder interface {
builder.APIGroupBuilder
builder.APIGroupMutation
builder.APIGroupValidation
SetApp(app app.App)
}
type appBuilder struct {
app app.App
config AppBuilderConfig
}
func NewAppBuilder(appBuilderConfig AppBuilderConfig) (*appBuilder, error) {
return &appBuilder{
config: appBuilderConfig,
}, nil
}
func (b *appBuilder) SetApp(app app.App) {
b.app = app
}
// GetGroupVersion implements APIGroupBuilder.GetGroupVersion
func (b *appBuilder) GetGroupVersion() schema.GroupVersion {
return b.config.groupVersion
}
// InstallSchema implements APIGroupBuilder.InstallSchema
func (b *appBuilder) InstallSchema(scheme *runtime.Scheme) error {
gv := b.GetGroupVersion()
for _, kinds := range b.config.ManagedKinds {
for _, kind := range kinds {
scheme.AddKnownTypeWithName(gv.WithKind(kind.Kind()), kind.ZeroValue())
scheme.AddKnownTypeWithName(gv.WithKind(kind.Kind()+"List"), kind.ZeroListValue())
// Link this group to the internal representation.
// This is used for server-side-apply (PATCH), and avoids the error:
// "no kind is registered for the type"
gvInternal := schema.GroupVersion{
Group: gv.Group,
Version: runtime.APIVersionInternal,
}
// only register internal kind once
if _, ok := scheme.KnownTypes(gvInternal)[kind.Kind()]; !ok {
scheme.AddKnownTypeWithName(gvInternal.WithKind(kind.Kind()), kind.ZeroValue())
scheme.AddKnownTypeWithName(gvInternal.WithKind(kind.Kind()+"List"), kind.ZeroListValue())
}
if len(kind.SelectableFields()) == 0 {
continue
}
gvk := gv.WithKind(kind.Kind())
err := scheme.AddFieldLabelConversionFunc(
gvk,
func(label, value string) (string, string, error) {
if label == "metadata.name" || label == "metadata.namespace" {
return label, value, nil
}
fields := kind.SelectableFields()
for _, field := range fields {
if field.FieldSelector == label {
return label, value, nil
}
}
return "", "", fmt.Errorf("field label not supported for %s: %s", gvk, label)
},
)
if err != nil {
return err
}
}
}
return scheme.SetVersionPriority(gv)
}
// AllowedV0Alpha1Resources returns the list of resources that are allowed to be accessed in v0alpha1
func (b *appBuilder) AllowedV0Alpha1Resources() []string {
return b.config.AllowedV0Alpha1Resources
}
// UpdateAPIGroupInfo implements APIGroupBuilder.UpdateAPIGroupInfo
func (b *appBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error {
for _, kinds := range b.config.ManagedKinds {
for _, kind := range kinds {
version := kind.GroupVersionKind().Version
if _, ok := apiGroupInfo.VersionedResourcesStorageMap[version]; !ok {
apiGroupInfo.VersionedResourcesStorageMap[version] = make(map[string]rest.Storage)
}
resourceInfo := KindToResourceInfo(kind)
store, err := b.getStorage(resourceInfo, opts)
if err != nil {
return err
}
apiGroupInfo.VersionedResourcesStorageMap[version][resourceInfo.StoragePath()] = store
if registryStore, ok := store.(*genericregistry.Store); ok {
for subPath := range kind.ZeroValue().GetSubresources() {
apiGroupInfo.VersionedResourcesStorageMap[version][resourceInfo.StoragePath(subPath)] = grafanaregistry.NewRegistryStatusStore(opts.Scheme, registryStore)
}
}
}
}
return nil
}
func (b *appBuilder) getStorage(resourceInfo utils.ResourceInfo, opts builder.APIGroupOptions) (grafanarest.Storage, error) {
store, err := grafanaregistry.NewRegistryStore(opts.Scheme, resourceInfo, opts.OptsGetter)
if err != nil {
return nil, err
}
if b.config.LegacyStorageGetter != nil && opts.DualWriteBuilder != nil {
if legacyStorage := b.config.LegacyStorageGetter(resourceInfo.GroupVersionResource()); legacyStorage != nil {
return opts.DualWriteBuilder(resourceInfo.GroupResource(), legacyStorage, store)
}
}
return store, nil
}
// GetOpenAPIDefinitions implements APIGroupBuilder.GetOpenAPIDefinitions
func (b *appBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
return b.config.OpenAPIDefGetter
}
// GetAuthorizer implements APIGroupBuilder.GetAuthorizer
func (b *appBuilder) GetAuthorizer() authorizer.Authorizer {
return b.config.Authorizer
}