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 15:31:58 -07:00
committed by GitHub
co-authored by Ryan McKinley
parent 60922e5f77
commit 440f9a6ffb
16 changed files with 237 additions and 88 deletions
-63
View File
@@ -1,63 +0,0 @@
package apis
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))
}
+61 -31
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
}
+20 -10
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
}
+1 -3
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"`
}
@@ -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
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,
)