K8s: APIGroupBuilder App SDK Support (#95638)
This commit is contained in:
@@ -0,0 +1,107 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
|
||||
"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 _ builder.APIGroupBuilder = (*AppBuilder)(nil)
|
||||
|
||||
type LegacyStorageGetter func(schema.GroupVersionResource) grafanarest.LegacyStorage
|
||||
|
||||
type AppBuilderConfig struct {
|
||||
Authorizer authorizer.Authorizer
|
||||
LegacyStorageGetter LegacyStorageGetter
|
||||
OpenAPIDefGetter common.GetOpenAPIDefinitions
|
||||
ManagedKinds map[schema.GroupVersion]resource.Kind
|
||||
CustomConfig any
|
||||
|
||||
groupVersion schema.GroupVersion
|
||||
}
|
||||
|
||||
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 _, kind := range b.config.ManagedKinds {
|
||||
scheme.AddKnownTypeWithName(gv.WithKind(kind.Kind()), kind.ZeroValue())
|
||||
scheme.AddKnownTypeWithName(gv.WithKind(kind.Kind()+"List"), kind.ZeroListValue())
|
||||
}
|
||||
return scheme.SetVersionPriority(gv)
|
||||
}
|
||||
|
||||
// UpdateAPIGroupInfo implements APIGroupBuilder.UpdateAPIGroupInfo
|
||||
func (b *AppBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error {
|
||||
for _, kind := range b.config.ManagedKinds {
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
||||
// GetAPIRoutes implements APIGroupBuilder.GetAPIRoutes
|
||||
func (b *AppBuilder) GetAPIRoutes() *builder.APIRoutes {
|
||||
// TODO: The API routes are not yet exposed by the app.App interface.
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetAuthorizer implements APIGroupBuilder.GetAuthorizer
|
||||
func (b AppBuilder) GetAuthorizer() authorizer.Authorizer {
|
||||
return b.config.Authorizer
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func KindToResourceInfo(kind resource.Kind) utils.ResourceInfo {
|
||||
return utils.NewResourceInfo(
|
||||
kind.Group(),
|
||||
kind.Version(),
|
||||
kind.GroupVersionResource().Resource,
|
||||
strings.ToLower(kind.Kind()),
|
||||
kind.Kind(),
|
||||
func() runtime.Object { return kind.ZeroValue() },
|
||||
func() runtime.Object { return kind.ZeroListValue() },
|
||||
utils.TableColumns{}, // TODO: this only supports the default columns
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
package runner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/grafana-app-sdk/app"
|
||||
"github.com/grafana/grafana-app-sdk/resource"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/builder"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
type RunnerConfig struct {
|
||||
RestConfigGetter func(context.Context) *rest.Config
|
||||
APIRegistrar builder.APIRegistrar
|
||||
}
|
||||
|
||||
func NewAPIGroupRunner(cfg RunnerConfig, providers ...app.Provider) (*APIGroupRunner, error) {
|
||||
groups := []appBuilderGroup{}
|
||||
for _, provider := range providers {
|
||||
created, err := newAppBuilderGroup(cfg, provider)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groups = append(groups, created)
|
||||
}
|
||||
return &APIGroupRunner{
|
||||
config: cfg,
|
||||
groups: groups,
|
||||
initialized: make(chan struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
type APIGroupRunner struct {
|
||||
config RunnerConfig
|
||||
groups []appBuilderGroup
|
||||
initialized chan struct{}
|
||||
}
|
||||
|
||||
func (r *APIGroupRunner) Run(ctx context.Context) error {
|
||||
<-r.initialized
|
||||
runner := app.NewMultiRunner()
|
||||
for _, g := range r.groups {
|
||||
runner.AddRunnable(g.app.Runner())
|
||||
}
|
||||
return runner.Run(ctx)
|
||||
}
|
||||
|
||||
func (r *APIGroupRunner) Init(ctx context.Context) error {
|
||||
defer close(r.initialized)
|
||||
restConfig := r.config.RestConfigGetter(ctx)
|
||||
if restConfig == nil {
|
||||
return fmt.Errorf("rest config is nil")
|
||||
}
|
||||
for i, g := range r.groups {
|
||||
customCfg := g.builders[0].config.CustomConfig
|
||||
appConfig := app.Config{
|
||||
KubeConfig: *restConfig,
|
||||
ManifestData: *g.provider.Manifest().ManifestData,
|
||||
SpecificConfig: customCfg,
|
||||
}
|
||||
app, err := g.provider.NewApp(appConfig)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
r.groups[i].setApp(app)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (r *APIGroupRunner) GetBuilders() []AppBuilder {
|
||||
builders := []AppBuilder{}
|
||||
for _, g := range r.groups {
|
||||
builders = append(builders, g.builders...)
|
||||
}
|
||||
return builders
|
||||
}
|
||||
|
||||
type appBuilderGroup struct {
|
||||
builders []AppBuilder
|
||||
provider app.Provider
|
||||
app app.App
|
||||
}
|
||||
|
||||
func newAppBuilderGroup(cfg RunnerConfig, provider app.Provider) (appBuilderGroup, error) {
|
||||
manifest := provider.Manifest()
|
||||
if manifest.Location.Type != app.ManifestLocationEmbedded {
|
||||
return appBuilderGroup{}, fmt.Errorf("app: %s has unsupported manifest location type: %s", manifest.ManifestData.AppName, manifest.Location.Type)
|
||||
}
|
||||
|
||||
group := appBuilderGroup{
|
||||
provider: provider,
|
||||
builders: []AppBuilder{},
|
||||
}
|
||||
|
||||
appBuilderConfig, ok := provider.SpecificConfig().(*AppBuilderConfig)
|
||||
if !ok {
|
||||
return group, fmt.Errorf("provider's SpecificConfig is not of type *AppBuilderConfig, got %T", provider.SpecificConfig())
|
||||
}
|
||||
|
||||
for gv, kinds := range appBuilderConfig.ManagedKinds {
|
||||
confCopy := *appBuilderConfig
|
||||
confCopy.ManagedKinds = map[schema.GroupVersion]resource.Kind{
|
||||
gv: kinds,
|
||||
}
|
||||
confCopy.groupVersion = gv
|
||||
b, err := NewAppBuilder(confCopy)
|
||||
if err != nil {
|
||||
return group, err
|
||||
}
|
||||
group.builders = append(group.builders, b)
|
||||
cfg.APIRegistrar.RegisterAPI(&b)
|
||||
}
|
||||
return group, nil
|
||||
}
|
||||
|
||||
func (g *appBuilderGroup) setApp(app app.App) {
|
||||
g.app = app
|
||||
for i, _ := range g.builders {
|
||||
g.builders[i].setApp(app)
|
||||
}
|
||||
}
|
||||
@@ -81,7 +81,7 @@ type Service interface {
|
||||
}
|
||||
|
||||
type RestConfigProvider interface {
|
||||
GetRestConfig() *clientrest.Config
|
||||
GetRestConfig(context.Context) *clientrest.Config
|
||||
}
|
||||
|
||||
type DirectRestConfigProvider interface {
|
||||
@@ -202,7 +202,10 @@ func ProvideService(
|
||||
return s, nil
|
||||
}
|
||||
|
||||
func (s *service) GetRestConfig() *clientrest.Config {
|
||||
func (s *service) GetRestConfig(ctx context.Context) *clientrest.Config {
|
||||
if err := s.BasicService.AwaitRunning(ctx); err != nil {
|
||||
return nil
|
||||
}
|
||||
return s.restConfig
|
||||
}
|
||||
|
||||
@@ -212,10 +215,14 @@ func (s *service) IsDisabled() bool {
|
||||
|
||||
// Run is an adapter for the BackgroundService interface.
|
||||
func (s *service) Run(ctx context.Context) error {
|
||||
if err := s.start(ctx); err != nil {
|
||||
if err := s.BasicService.StartAsync(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.running(ctx)
|
||||
|
||||
if err := s.BasicService.AwaitRunning(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
return s.AwaitTerminated(ctx)
|
||||
}
|
||||
|
||||
func (s *service) RegisterAPI(b builder.APIGroupBuilder) {
|
||||
|
||||
Reference in New Issue
Block a user