K8s: Register apiserver as background service, and list real playlists (#75338)

Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
Todd Treece
2023-09-25 18:31:58 -04:00
committed by GitHub
parent 60922e5f77
commit 440f9a6ffb
16 changed files with 237 additions and 88 deletions

View File

@@ -4,13 +4,14 @@ import (
"context"
"fmt"
"github.com/grafana/grafana/pkg/apis"
"github.com/grafana/grafana/pkg/services/playlist"
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/registry/rest"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/playlist"
)
var _ rest.Scoper = (*handler)(nil)
@@ -47,54 +48,83 @@ func (r *handler) ConvertToTable(ctx context.Context, object runtime.Object, tab
func (r *handler) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
ns, ok := request.NamespaceFrom(ctx)
if ok && ns != "" {
orgId, err := apis.NamespaceToOrgID(ns)
if err != nil {
return nil, err
}
fmt.Printf("OrgID: %d\n", orgId)
if !ok || ns == "" {
return nil, fmt.Errorf("namespace required")
}
// TODO: replace
return &PlaylistList{
orgId, err := grafanaapiserver.NamespaceToOrgID(ns)
if err != nil {
return nil, err
}
limit := 100
if options.Limit > 0 {
limit = int(options.Limit)
}
res, err := r.service.Search(ctx, &playlist.GetPlaylistsQuery{
OrgId: orgId,
Limit: limit,
})
if err != nil {
return nil, err
}
list := &PlaylistList{
TypeMeta: metav1.TypeMeta{
Kind: "PlaylistList",
APIVersion: APIVersion,
},
Items: []Playlist{
{
TypeMeta: metav1.TypeMeta{
Kind: "Playlist",
APIVersion: APIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: "test",
},
Name: "test",
}
for _, v := range res {
p := Playlist{
TypeMeta: metav1.TypeMeta{
Kind: "Playlist",
APIVersion: APIVersion,
},
},
}, nil
ObjectMeta: metav1.ObjectMeta{
Name: v.UID,
},
}
p.Name = v.Name + " // " + v.Interval
list.Items = append(list.Items, p)
// TODO?? if table... we don't need the body of each, otherwise full lookup!
}
if len(list.Items) == limit {
list.Continue = "<more>" // TODO?
}
return list, nil
}
func (r *handler) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
ns, ok := request.NamespaceFrom(ctx)
if ok && ns != "" {
orgId, err := apis.NamespaceToOrgID(ns)
if err != nil {
return nil, err
}
fmt.Printf("OrgID: %d\n", orgId)
if !ok || ns == "" {
return nil, fmt.Errorf("namespace required")
}
orgId, err := grafanaapiserver.NamespaceToOrgID(ns)
if err != nil {
return nil, err
}
p, err := r.service.Get(ctx, &playlist.GetPlaylistByUidQuery{
UID: name,
OrgId: orgId,
})
if err != nil {
return nil, err
}
if p == nil {
return nil, fmt.Errorf("not found?")
}
// TODO: replace
return &Playlist{
TypeMeta: metav1.TypeMeta{
Kind: "Playlist",
APIVersion: APIVersion,
},
ObjectMeta: metav1.ObjectMeta{
Name: name,
Name: p.Uid,
},
Name: "test",
Name: p.Name + "//" + p.Interval,
}, nil
}

View File

@@ -1,7 +1,6 @@
package v1
import (
"github.com/grafana/grafana/pkg/apis"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
@@ -10,6 +9,9 @@ import (
genericapiserver "k8s.io/apiserver/pkg/server"
common "k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/playlist"
)
// GroupName is the group name for this API.
@@ -17,14 +19,22 @@ const GroupName = "playlist.x.grafana.com"
const VersionID = "v0-alpha" //
const APIVersion = GroupName + "/" + VersionID
type builder struct{}
var _ grafanaapiserver.APIGroupBuilder = (*PlaylistAPIBuilder)(nil)
// TODO.. this will have wire dependencies
func GetAPIGroupBuilder() apis.APIGroupBuilder {
return &builder{}
// This is used just so wire has something unique to return
type PlaylistAPIBuilder struct {
service playlist.Service
}
func (b *builder) InstallSchema(scheme *runtime.Scheme) error {
func RegisterAPIService(p playlist.Service, apiregistration grafanaapiserver.APIRegistrar) *PlaylistAPIBuilder {
builder := &PlaylistAPIBuilder{
service: p,
}
apiregistration.RegisterAPI(builder)
return builder
}
func (b *PlaylistAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
err := AddToScheme(scheme)
if err != nil {
return err
@@ -32,26 +42,26 @@ func (b *builder) InstallSchema(scheme *runtime.Scheme) error {
return scheme.SetVersionPriority(SchemeGroupVersion)
}
func (b *builder) GetAPIGroupInfo(
func (b *PlaylistAPIBuilder) GetAPIGroupInfo(
scheme *runtime.Scheme,
codecs serializer.CodecFactory, // pointer?
) *genericapiserver.APIGroupInfo {
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(GroupName, scheme, metav1.ParameterCodec, codecs)
storage := map[string]rest.Storage{}
storage["playlists"] = &handler{
service: nil, // TODO!!!!!
service: b.service,
}
apiGroupInfo.VersionedResourcesStorageMap[VersionID] = storage
return &apiGroupInfo
}
func (b *builder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
func (b *PlaylistAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
return getOpenAPIDefinitions
}
// Register additional routes with the server
func (b *builder) GetOpenAPIPostProcessor() func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
func (b *PlaylistAPIBuilder) GetOpenAPIPostProcessor() func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
return nil
}

View File

@@ -18,10 +18,8 @@ type Playlist struct {
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
type PlaylistList struct {
metav1.TypeMeta `json:",inline"`
// Standard object's metadata
// More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata
// +optional
metav1.ObjectMeta `json:"metadata,omitempty"`
metav1.ListMeta `json:"metadata,omitempty"`
Items []Playlist `json:"playlists,omitempty"`
}

View File

@@ -41,7 +41,7 @@ func (in *Playlist) DeepCopyObject() runtime.Object {
func (in *PlaylistList) DeepCopyInto(out *PlaylistList) {
*out = *in
out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
in.ListMeta.DeepCopyInto(&out.ListMeta)
if in.Items != nil {
in, out := &in.Items, &out.Items
*out = make([]Playlist, len(*in))

10
pkg/apis/wireset.go Normal file
View File

@@ -0,0 +1,10 @@
package apis
import (
"github.com/google/wire"
playlistv1 "github.com/grafana/grafana/pkg/apis/playlist/v1"
)
var WireSet = wire.NewSet(
playlistv1.RegisterAPIService,
)

25
pkg/registry/apis/apis.go Normal file
View File

@@ -0,0 +1,25 @@
package apiregistry
import (
"context"
playlistsv1 "github.com/grafana/grafana/pkg/apis/playlist/v1"
"github.com/grafana/grafana/pkg/registry"
)
var (
_ registry.BackgroundService = (*Service)(nil)
)
type Service struct{}
func ProvideService(
_ *playlistsv1.PlaylistAPIBuilder,
) *Service {
return &Service{}
}
func (s *Service) Run(ctx context.Context) error {
<-ctx.Done()
return nil
}

View File

@@ -0,0 +1,12 @@
package apiregistry
import (
"github.com/google/wire"
"github.com/grafana/grafana/pkg/apis"
)
var WireSet = wire.NewSet(
ProvideService,
apis.WireSet,
)

View File

@@ -8,11 +8,13 @@ import (
uss "github.com/grafana/grafana/pkg/infra/usagestats/service"
"github.com/grafana/grafana/pkg/infra/usagestats/statscollector"
"github.com/grafana/grafana/pkg/registry"
apiregistry "github.com/grafana/grafana/pkg/registry/apis"
"github.com/grafana/grafana/pkg/services/alerting"
"github.com/grafana/grafana/pkg/services/anonymous/anonimpl"
"github.com/grafana/grafana/pkg/services/auth"
"github.com/grafana/grafana/pkg/services/cleanup"
"github.com/grafana/grafana/pkg/services/dashboardsnapshots"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grpcserver"
"github.com/grafana/grafana/pkg/services/guardian"
ldapapi "github.com/grafana/grafana/pkg/services/ldap/api"
@@ -51,16 +53,16 @@ func ProvideBackgroundServiceRegistry(
secretsService *secretsManager.SecretsService, remoteCache *remotecache.RemoteCache, StorageService store.StorageService, searchService searchV2.SearchService, entityEventsService store.EntityEventsService,
saService *samanager.ServiceAccountsService, authInfoService *authinfoservice.Implementation,
grpcServerProvider grpcserver.Provider, secretMigrationProvider secretsMigrations.SecretMigrationProvider, loginAttemptService *loginattemptimpl.Service,
bundleService *supportbundlesimpl.Service,
publicDashboardsMetric *publicdashboardsmetric.Service,
keyRetriever *dynamic.KeyRetriever,
dynamicAngularDetectorsProvider *angulardetectorsprovider.Dynamic,
bundleService *supportbundlesimpl.Service, publicDashboardsMetric *publicdashboardsmetric.Service,
keyRetriever *dynamic.KeyRetriever, dynamicAngularDetectorsProvider *angulardetectorsprovider.Dynamic,
grafanaAPIServer grafanaapiserver.Service,
anon *anonimpl.AnonDeviceService,
// Need to make sure these are initialized, is there a better place to put them?
_ dashboardsnapshots.Service, _ *alerting.AlertNotificationService,
_ serviceaccounts.Service, _ *guardian.Provider,
_ *plugindashboardsservice.DashboardUpdater, _ *sanitizer.Provider,
_ *grpcserver.HealthService, _ entity.EntityStoreServer, _ *grpcserver.ReflectionService, _ *ldapapi.Service,
_ *apiregistry.Service,
) *BackgroundServiceRegistry {
return NewBackgroundServiceRegistry(
httpServer,
@@ -94,6 +96,7 @@ func ProvideBackgroundServiceRegistry(
publicDashboardsMetric,
keyRetriever,
dynamicAngularDetectorsProvider,
grafanaAPIServer,
anon,
)
}

View File

@@ -5,7 +5,6 @@ import (
"fmt"
"net"
"os"
"path"
"path/filepath"
"strconv"
"sync"
@@ -16,7 +15,6 @@ import (
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/modules"
"github.com/grafana/grafana/pkg/services/featuremgmt"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/setting"
)
@@ -122,13 +120,14 @@ func (s *ModuleServer) Run() error {
return NewService(s.cfg, s.opts, s.apiOpts)
})
if s.features.IsEnabled(featuremgmt.FlagGrafanaAPIServer) {
m.RegisterModule(modules.GrafanaAPIServer, func() (services.Service, error) {
return grafanaapiserver.New(path.Join(s.cfg.DataPath, "k8s"))
})
} else {
s.log.Debug("apiserver feature is disabled")
}
// TODO: uncomment this once the apiserver is ready to be run as a standalone target
//if s.features.IsEnabled(featuremgmt.FlagGrafanaAPIServer) {
// m.RegisterModule(modules.GrafanaAPIServer, func() (services.Service, error) {
// return grafanaapiserver.New(path.Join(s.cfg.DataPath, "k8s"))
// })
//} else {
// s.log.Debug("apiserver feature is disabled")
//}
m.RegisterModule(modules.All, nil)

View File

@@ -33,6 +33,7 @@ import (
"github.com/grafana/grafana/pkg/login/social"
"github.com/grafana/grafana/pkg/middleware/csrf"
"github.com/grafana/grafana/pkg/middleware/loggermw"
apiregistry "github.com/grafana/grafana/pkg/registry/apis"
"github.com/grafana/grafana/pkg/registry/corekind"
"github.com/grafana/grafana/pkg/services/accesscontrol"
"github.com/grafana/grafana/pkg/services/accesscontrol/acimpl"
@@ -63,6 +64,7 @@ import (
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/services/folder"
"github.com/grafana/grafana/pkg/services/folder/folderimpl"
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
"github.com/grafana/grafana/pkg/services/grpcserver"
grpccontext "github.com/grafana/grafana/pkg/services/grpcserver/context"
"github.com/grafana/grafana/pkg/services/grpcserver/interceptors"
@@ -359,6 +361,8 @@ var wireBasicSet = wire.NewSet(
loggermw.Provide,
signingkeysimpl.ProvideEmbeddedSigningKeysService,
wire.Bind(new(signingkeys.Service), new(*signingkeysimpl.Service)),
grafanaapiserver.WireSet,
apiregistry.WireSet,
)
var wireSet = wire.NewSet(

View File

@@ -1,4 +1,4 @@
package apis
package grafanaapiserver
import (
"fmt"

View File

@@ -1,7 +1,6 @@
package grafanaapiserver
import (
"github.com/grafana/grafana/pkg/apis"
"golang.org/x/exp/maps"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
common "k8s.io/kube-openapi/pkg/common"
@@ -9,7 +8,7 @@ import (
)
// This should eventually live in grafana-app-sdk
func getOpenAPIDefinitions(builders []apis.APIGroupBuilder) common.GetOpenAPIDefinitions {
func getOpenAPIDefinitions(builders []APIGroupBuilder) common.GetOpenAPIDefinitions {
return func(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
defs := getStandardOpenAPIDefinitions(ref)
for _, builder := range builders {

View File

@@ -5,6 +5,8 @@ import (
"crypto/x509"
"net"
"path"
"strconv"
"strings"
"github.com/go-logr/logr"
"github.com/grafana/dskit/services"
@@ -18,6 +20,7 @@ import (
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
"k8s.io/apiserver/pkg/authentication/user"
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
"k8s.io/apiserver/pkg/endpoints/responsewriter"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/apiserver/pkg/server/options"
"k8s.io/apiserver/pkg/util/openapi"
@@ -27,9 +30,15 @@ import (
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
"k8s.io/klog/v2"
"github.com/grafana/grafana/pkg/apis"
playlistv1 "github.com/grafana/grafana/pkg/apis/playlist/v1"
"github.com/grafana/grafana/pkg/api/routing"
"github.com/grafana/grafana/pkg/infra/appcontext"
"github.com/grafana/grafana/pkg/middleware"
"github.com/grafana/grafana/pkg/modules"
"github.com/grafana/grafana/pkg/registry"
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
"github.com/grafana/grafana/pkg/services/featuremgmt"
"github.com/grafana/grafana/pkg/setting"
"github.com/grafana/grafana/pkg/web"
)
const (
@@ -37,15 +46,16 @@ const (
)
var (
_ Service = (*service)(nil)
_ RestConfigProvider = (*service)(nil)
_ Service = (*service)(nil)
_ RestConfigProvider = (*service)(nil)
_ registry.BackgroundService = (*service)(nil)
_ registry.CanBeDisabled = (*service)(nil)
)
var (
Scheme = runtime.NewScheme()
Codecs = serializer.NewCodecFactory(Scheme)
// if you modify this, make sure you update the crEncoder
unversionedVersion = schema.GroupVersion{Group: "", Version: "v1"}
unversionedTypes = []runtime.Object{
&metav1.Status{},
@@ -65,6 +75,12 @@ func init() {
type Service interface {
services.NamedService
registry.BackgroundService
registry.CanBeDisabled
}
type APIRegistrar interface {
RegisterAPI(builder APIGroupBuilder)
}
type RestConfigProvider interface {
@@ -76,19 +92,50 @@ type service struct {
restConfig *clientrest.Config
enabled bool
dataPath string
stopCh chan struct{}
stoppedCh chan error
rr routing.RouteRegister
handler web.Handler
builders []APIGroupBuilder
}
func New(dataPath string) (*service, error) {
func ProvideService(cfg *setting.Cfg,
rr routing.RouteRegister,
) (*service, error) {
s := &service{
dataPath: dataPath,
enabled: cfg.IsFeatureToggleEnabled(featuremgmt.FlagGrafanaAPIServer),
rr: rr,
dataPath: path.Join(cfg.DataPath, "k8s"),
stopCh: make(chan struct{}),
builders: []APIGroupBuilder{},
}
// This will be used when running as a dskit service
s.BasicService = services.NewBasicService(s.start, s.running, nil).WithName(modules.GrafanaAPIServer)
// TODO: this is very hacky
// We need to register the routes in ProvideService to make sure
// the routes are registered before the Grafana HTTP server starts.
s.rr.Group("/k8s", func(k8sRoute routing.RouteRegister) {
handler := func(c *contextmodel.ReqContext) {
if s.handler == nil {
c.Resp.WriteHeader(404)
_, _ = c.Resp.Write([]byte("Not found"))
return
}
if handle, ok := s.handler.(func(c *contextmodel.ReqContext)); ok {
handle(c)
return
}
}
k8sRoute.Any("/", middleware.ReqSignedIn, handler)
k8sRoute.Any("/*", middleware.ReqSignedIn, handler)
})
return s, nil
}
@@ -96,6 +143,22 @@ func (s *service) GetRestConfig() *clientrest.Config {
return s.restConfig
}
func (s *service) IsDisabled() bool {
return !s.enabled
}
// Run is an adapter for the BackgroundService interface.
func (s *service) Run(ctx context.Context) error {
if err := s.start(ctx); err != nil {
return err
}
return s.running(ctx)
}
func (s *service) RegisterAPI(builder APIGroupBuilder) {
s.builders = append(s.builders, builder)
}
func (s *service) start(ctx context.Context) error {
logger := logr.New(newLogAdapter())
logger.V(9)
@@ -153,9 +216,7 @@ func (s *service) start(ctx context.Context) error {
serverConfig.Authentication.Authenticator = authenticator
// Get the list of groups the server will support
builders := []apis.APIGroupBuilder{
playlistv1.GetAPIGroupBuilder(),
}
builders := s.builders
// Install schemas
for _, b := range builders {
@@ -199,10 +260,8 @@ func (s *service) start(ctx context.Context) error {
prepared := server.PrepareRun()
// TODO: not sure if we can still inject RouteRegister with the new module server setup
// Disabling the /k8s endpoint until we have a solution
/* handler := func(c *contextmodel.ReqContext) {
// TODO: this is a hack. see note in ProvideService
s.handler = func(c *contextmodel.ReqContext) {
req := c.Req
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/k8s")
if req.URL.Path == "" {
@@ -221,10 +280,6 @@ func (s *service) start(ctx context.Context) error {
resp := responsewriter.WrapForHTTP1Or2(c.Resp)
prepared.GenericAPIServer.Handler.ServeHTTP(resp, req)
}
/* s.rr.Group("/k8s", func(k8sRoute routing.RouteRegister) {
k8sRoute.Any("/", middleware.ReqSignedIn, handler)
k8sRoute.Any("/*", middleware.ReqSignedIn, handler)
}) */
go func() {
s.stoppedCh <- prepared.Run(s.stopCh)

View File

@@ -0,0 +1,12 @@
package grafanaapiserver
import (
"github.com/google/wire"
)
var WireSet = wire.NewSet(
ProvideService,
wire.Bind(new(RestConfigProvider), new(*service)),
wire.Bind(new(Service), new(*service)),
wire.Bind(new(APIRegistrar), new(*service)),
)