mirror of
https://github.com/grafana/grafana.git
synced 2024-11-23 09:26:43 -06:00
K8s/Playlist: Isolate apiGroup from server (#75321)
This commit is contained in:
parent
042833376a
commit
e72b5c54f8
63
pkg/apis/common.go
Normal file
63
pkg/apis/common.go
Normal file
@ -0,0 +1,63 @@
|
||||
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))
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
package install
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
|
||||
playlistv1 "github.com/grafana/grafana/pkg/apis/playlist/v1"
|
||||
)
|
||||
|
||||
// Install registers the API group and adds types to a scheme
|
||||
func Install(scheme *runtime.Scheme) {
|
||||
utilruntime.Must(playlistv1.AddToScheme(scheme))
|
||||
utilruntime.Must(scheme.SetVersionPriority(playlistv1.SchemeGroupVersion))
|
||||
}
|
@ -2,55 +2,70 @@ package v1
|
||||
|
||||
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"
|
||||
)
|
||||
|
||||
var _ rest.Scoper = (*Handler)(nil)
|
||||
var _ rest.SingularNameProvider = (*Handler)(nil)
|
||||
var _ rest.Getter = (*Handler)(nil)
|
||||
var _ rest.Lister = (*Handler)(nil)
|
||||
var _ rest.Storage = (*Handler)(nil)
|
||||
var _ rest.Scoper = (*handler)(nil)
|
||||
var _ rest.SingularNameProvider = (*handler)(nil)
|
||||
var _ rest.Getter = (*handler)(nil)
|
||||
var _ rest.Lister = (*handler)(nil)
|
||||
var _ rest.Storage = (*handler)(nil)
|
||||
|
||||
type Handler struct{}
|
||||
type handler struct {
|
||||
service playlist.Service
|
||||
}
|
||||
|
||||
func (r *Handler) New() runtime.Object {
|
||||
func (r *handler) New() runtime.Object {
|
||||
return &Playlist{}
|
||||
}
|
||||
|
||||
func (r *Handler) Destroy() {}
|
||||
func (r *handler) Destroy() {}
|
||||
|
||||
func (r *Handler) NamespaceScoped() bool {
|
||||
return true
|
||||
func (r *handler) NamespaceScoped() bool {
|
||||
return true // namespace == org
|
||||
}
|
||||
|
||||
func (r *Handler) GetSingularName() string {
|
||||
func (r *handler) GetSingularName() string {
|
||||
return "playlist"
|
||||
}
|
||||
|
||||
func (r *Handler) NewList() runtime.Object {
|
||||
func (r *handler) NewList() runtime.Object {
|
||||
return &PlaylistList{}
|
||||
}
|
||||
|
||||
func (r *Handler) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
func (r *handler) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return rest.NewDefaultTableConvertor(Resource("playlists")).ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
func (r *Handler) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
// TODO: replace
|
||||
return &PlaylistList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PlaylistList",
|
||||
APIVersion: "playlist.grafana.io/v1",
|
||||
APIVersion: APIVersion,
|
||||
},
|
||||
Items: []Playlist{
|
||||
{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Playlist",
|
||||
APIVersion: "playlist.grafana.io/v1",
|
||||
APIVersion: APIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "test",
|
||||
@ -61,12 +76,21 @@ func (r *Handler) List(ctx context.Context, options *internalversion.ListOptions
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (r *Handler) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
// TODO: replace
|
||||
return &Playlist{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Playlist",
|
||||
APIVersion: "playlist.grafana.io/v1",
|
||||
APIVersion: APIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
|
115
pkg/apis/playlist/v1/openapi.go
Normal file
115
pkg/apis/playlist/v1/openapi.go
Normal file
@ -0,0 +1,115 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
common "k8s.io/kube-openapi/pkg/common"
|
||||
spec "k8s.io/kube-openapi/pkg/validation/spec"
|
||||
)
|
||||
|
||||
// NOTE: this must match the golang fully qualifid name!
|
||||
const kindKey = "github.com/grafana/grafana/pkg/apis/playlist/v1.Playlist"
|
||||
|
||||
func getOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||
return map[string]common.OpenAPIDefinition{
|
||||
kindKey: schema_pkg_Playlist(ref),
|
||||
kindKey + "List": schema_pkg_PlaylistList(ref),
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_Playlist(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Playlist",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"),
|
||||
},
|
||||
},
|
||||
// TODO! add a real spec here
|
||||
// "spec": {
|
||||
// SchemaProps: spec.SchemaProps{
|
||||
// Default: map[string]interface{}{},
|
||||
// Ref: ref("github.com/grafana/google-sheets-datasource/pkg/apis/googlesheets/v1.DatasourceSpec"),
|
||||
// },
|
||||
// },
|
||||
// "status": {
|
||||
// SchemaProps: spec.SchemaProps{
|
||||
// Default: map[string]interface{}{},
|
||||
// Ref: ref("github.com/grafana/google-sheets-datasource/pkg/apis/googlesheets/v1.DatasourceStatus"),
|
||||
// },
|
||||
// },
|
||||
},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta",
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_PlaylistList(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "PlaylistList",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"kind": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"apiVersion": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"metadata": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||
},
|
||||
},
|
||||
"items": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref(kindKey),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
kindKey,
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
@ -1,16 +1,62 @@
|
||||
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"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
common "k8s.io/kube-openapi/pkg/common"
|
||||
"k8s.io/kube-openapi/pkg/spec3"
|
||||
)
|
||||
|
||||
// GroupName is the group name for this API.
|
||||
const GroupName = "playlist.grafana.io"
|
||||
const GroupName = "playlist.x.grafana.com"
|
||||
const VersionID = "v0-alpha" //
|
||||
const APIVersion = GroupName + "/" + VersionID
|
||||
|
||||
type builder struct{}
|
||||
|
||||
// TODO.. this will have wire dependencies
|
||||
func GetAPIGroupBuilder() apis.APIGroupBuilder {
|
||||
return &builder{}
|
||||
}
|
||||
|
||||
func (b *builder) InstallSchema(scheme *runtime.Scheme) error {
|
||||
err := AddToScheme(scheme)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return scheme.SetVersionPriority(SchemeGroupVersion)
|
||||
}
|
||||
|
||||
func (b *builder) 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!!!!!
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[VersionID] = storage
|
||||
return &apiGroupInfo
|
||||
}
|
||||
|
||||
func (b *builder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
||||
return getOpenAPIDefinitions
|
||||
}
|
||||
|
||||
// Register additional routes with the server
|
||||
func (b *builder) GetOpenAPIPostProcessor() func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// SchemeGroupVersion is group version used to register these objects
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
|
||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: VersionID}
|
||||
|
||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
||||
func Resource(resource string) schema.GroupResource {
|
||||
|
@ -71,17 +71,17 @@ func (in *PlaylistList) DeepCopyObject() runtime.Object {
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *Handler) DeepCopyInto(out *Handler) {
|
||||
func (in *handler) DeepCopyInto(out *handler) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Storage.
|
||||
func (in *Handler) DeepCopy() *Handler {
|
||||
func (in *handler) DeepCopy() *handler {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(Handler)
|
||||
out := new(handler)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
2511
pkg/services/grafana-apiserver/openapi.go
Normal file
2511
pkg/services/grafana-apiserver/openapi.go
Normal file
File diff suppressed because it is too large
Load Diff
@ -17,15 +17,17 @@ import (
|
||||
"k8s.io/apiserver/pkg/authentication/authenticator"
|
||||
"k8s.io/apiserver/pkg/authentication/request/headerrequest"
|
||||
"k8s.io/apiserver/pkg/authentication/user"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
openapinamer "k8s.io/apiserver/pkg/endpoints/openapi"
|
||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||
"k8s.io/apiserver/pkg/server/options"
|
||||
"k8s.io/apiserver/pkg/util/openapi"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
clientrest "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/tools/clientcmd"
|
||||
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apis/install"
|
||||
"github.com/grafana/grafana/pkg/apis"
|
||||
playlistv1 "github.com/grafana/grafana/pkg/apis/playlist/v1"
|
||||
"github.com/grafana/grafana/pkg/modules"
|
||||
)
|
||||
@ -56,7 +58,6 @@ var (
|
||||
)
|
||||
|
||||
func init() {
|
||||
install.Install(Scheme)
|
||||
// we need to add the options to empty v1
|
||||
metav1.AddToGroupVersion(Scheme, schema.GroupVersion{Group: "", Version: "v1"})
|
||||
Scheme.AddUnversionedTypes(unversionedVersion, unversionedTypes...)
|
||||
@ -151,18 +152,43 @@ 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(),
|
||||
}
|
||||
|
||||
// Install schemas
|
||||
for _, b := range builders {
|
||||
err = b.InstallSchema(Scheme) // previously was in init
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// Add OpenAPI specs for each group+version
|
||||
defsGetter := getOpenAPIDefinitions(builders)
|
||||
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
|
||||
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
|
||||
openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme))
|
||||
|
||||
serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config(
|
||||
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),
|
||||
openapinamer.NewDefinitionNamer(Scheme, scheme.Scheme))
|
||||
|
||||
serverConfig.SkipOpenAPIInstallation = false
|
||||
|
||||
// Create the server
|
||||
server, err := serverConfig.Complete().New("grafana-apiserver", genericapiserver.NewEmptyDelegate())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(playlistv1.GroupName, Scheme, metav1.ParameterCodec, Codecs)
|
||||
playlistv1storage := map[string]rest.Storage{}
|
||||
playlistv1storage["playlists"] = &playlistv1.Handler{}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap["v1"] = playlistv1storage
|
||||
if err := server.InstallAPIGroup(&apiGroupInfo); err != nil {
|
||||
return err
|
||||
// Install the API Group+version
|
||||
for _, b := range builders {
|
||||
err = server.InstallAPIGroup(b.GetAPIGroupInfo(Scheme, Codecs))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
s.restConfig = server.LoopbackClientConfig
|
||||
|
Loading…
Reference in New Issue
Block a user