mirror of
https://github.com/grafana/grafana.git
synced 2025-02-25 18:55:37 -06:00
K8s: Register apiserver as background service, and list real playlists (#75338)
Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
63
pkg/services/grafana-apiserver/common.go
Normal file
63
pkg/services/grafana-apiserver/common.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package grafanaapiserver
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/kube-openapi/pkg/common"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
)
|
||||
|
||||
// TODO: this (or something like it) belongs in grafana-app-sdk,
|
||||
// but lets keep it here while we iterate on a few simple examples
|
||||
type APIGroupBuilder interface {
|
||||
// Add the kinds to the server scheme
|
||||
InstallSchema(scheme *runtime.Scheme) error
|
||||
|
||||
// Build the group+version behavior
|
||||
GetAPIGroupInfo(
|
||||
scheme *runtime.Scheme,
|
||||
codecs serializer.CodecFactory, // pointer?
|
||||
) *genericapiserver.APIGroupInfo
|
||||
|
||||
// Get OpenAPI definitions
|
||||
GetOpenAPIDefinitions() common.GetOpenAPIDefinitions
|
||||
|
||||
// Register additional routes with the server
|
||||
GetOpenAPIPostProcessor() func(*spec3.OpenAPI) (*spec3.OpenAPI, error)
|
||||
}
|
||||
|
||||
func OrgIdToNamespace(orgId int64) string {
|
||||
if orgId > 1 {
|
||||
return fmt.Sprintf("org-%d", orgId)
|
||||
}
|
||||
return "default"
|
||||
}
|
||||
|
||||
func NamespaceToOrgID(ns string) (int64, error) {
|
||||
parts := strings.Split(ns, "-")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
if parts[0] == "default" {
|
||||
return 1, nil
|
||||
}
|
||||
if parts[0] == "" {
|
||||
return 0, nil // no orgId, cluster scope
|
||||
}
|
||||
return 0, fmt.Errorf("invalid namespace (expected default)")
|
||||
case 2:
|
||||
if !(parts[0] == "org" || parts[0] == "tenant") {
|
||||
return 0, fmt.Errorf("invalid namespace (org|tenant)")
|
||||
}
|
||||
n, err := strconv.ParseInt(parts[1], 10, 64)
|
||||
if err != nil {
|
||||
return 0, fmt.Errorf("invalid namepscae (%w)", err)
|
||||
}
|
||||
return n, nil
|
||||
}
|
||||
return 0, fmt.Errorf("invalid namespace (%d parts)", len(parts))
|
||||
}
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
12
pkg/services/grafana-apiserver/wireset.go
Normal file
12
pkg/services/grafana-apiserver/wireset.go
Normal 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)),
|
||||
)
|
||||
Reference in New Issue
Block a user