K8s: Add storage dual writer (#75403)
This commit is contained in:
@@ -2,4 +2,4 @@
|
||||
// +k8s:openapi-gen=true
|
||||
// +groupName=playlist.grafana.io
|
||||
|
||||
package v1 // import "github.com/grafana/grafana/pkg/apis/playlist/v1"
|
||||
package v0alpha1 // import "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
|
||||
@@ -0,0 +1,128 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
grafanarequest "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*legacyStorage)(nil)
|
||||
_ rest.SingularNameProvider = (*legacyStorage)(nil)
|
||||
_ rest.Getter = (*legacyStorage)(nil)
|
||||
_ rest.Lister = (*legacyStorage)(nil)
|
||||
_ rest.Storage = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
type legacyStorage struct {
|
||||
service playlist.Service
|
||||
}
|
||||
|
||||
func newLegacyStorage(s playlist.Service) *legacyStorage {
|
||||
return &legacyStorage{
|
||||
service: s,
|
||||
}
|
||||
}
|
||||
|
||||
func (s *legacyStorage) New() runtime.Object {
|
||||
return &Playlist{}
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Destroy() {}
|
||||
|
||||
func (s *legacyStorage) NamespaceScoped() bool {
|
||||
return true // namespace == org
|
||||
}
|
||||
|
||||
func (s *legacyStorage) GetSingularName() string {
|
||||
return "playlist"
|
||||
}
|
||||
|
||||
func (s *legacyStorage) NewList() runtime.Object {
|
||||
return &PlaylistList{}
|
||||
}
|
||||
|
||||
func (s *legacyStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return rest.NewDefaultTableConvertor(Resource("playlists")).ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
// TODO: handle fetching all available orgs when no namespace is specified
|
||||
// To test: kubectl get playlists --all-namespaces
|
||||
orgId, ok := grafanarequest.OrgIDFrom(ctx)
|
||||
if !ok {
|
||||
orgId = 1 // TODO: default org ID 1 for now
|
||||
}
|
||||
|
||||
limit := 100
|
||||
if options.Limit > 0 {
|
||||
limit = int(options.Limit)
|
||||
}
|
||||
res, err := s.service.Search(ctx, &playlist.GetPlaylistsQuery{
|
||||
OrgId: orgId,
|
||||
Limit: limit,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := &PlaylistList{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "PlaylistList",
|
||||
APIVersion: APIVersion,
|
||||
},
|
||||
}
|
||||
for _, v := range res {
|
||||
p := Playlist{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Playlist",
|
||||
APIVersion: APIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: v.UID,
|
||||
},
|
||||
}
|
||||
p.Name = v.Name + " // " + v.Interval
|
||||
list.Items = append(list.Items, p)
|
||||
}
|
||||
if len(list.Items) == limit {
|
||||
list.Continue = "<more>" // TODO?
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
orgId, ok := grafanarequest.OrgIDFrom(ctx)
|
||||
if !ok {
|
||||
orgId = 1 // TODO: default org ID 1 for now
|
||||
}
|
||||
|
||||
p, err := s.service.Get(ctx, &playlist.GetPlaylistByUidQuery{
|
||||
UID: name,
|
||||
OrgId: orgId,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if p == nil {
|
||||
return nil, fmt.Errorf("not found?")
|
||||
}
|
||||
|
||||
return &Playlist{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Playlist",
|
||||
APIVersion: APIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: p.Uid,
|
||||
},
|
||||
Name: p.Name + "//" + p.Interval,
|
||||
}, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
common "k8s.io/kube-openapi/pkg/common"
|
||||
@@ -6,7 +6,7 @@ import (
|
||||
)
|
||||
|
||||
// NOTE: this must match the golang fully qualifid name!
|
||||
const kindKey = "github.com/grafana/grafana/pkg/apis/playlist/v1.Playlist"
|
||||
const kindKey = "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1.Playlist"
|
||||
|
||||
func getOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||
return map[string]common.OpenAPIDefinition{
|
||||
@@ -1,22 +1,24 @@
|
||||
package v1
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
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/generic"
|
||||
"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"
|
||||
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
|
||||
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
)
|
||||
|
||||
// GroupName is the group name for this API.
|
||||
const GroupName = "playlist.x.grafana.com"
|
||||
const VersionID = "v0-alpha" //
|
||||
const VersionID = "v0alpha1" //
|
||||
const APIVersion = GroupName + "/" + VersionID
|
||||
|
||||
var _ grafanaapiserver.APIGroupBuilder = (*PlaylistAPIBuilder)(nil)
|
||||
@@ -45,15 +47,25 @@ func (b *PlaylistAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
||||
func (b *PlaylistAPIBuilder) GetAPIGroupInfo(
|
||||
scheme *runtime.Scheme,
|
||||
codecs serializer.CodecFactory, // pointer?
|
||||
) *genericapiserver.APIGroupInfo {
|
||||
optsGetter generic.RESTOptionsGetter,
|
||||
) (*genericapiserver.APIGroupInfo, error) {
|
||||
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(GroupName, scheme, metav1.ParameterCodec, codecs)
|
||||
storage := map[string]rest.Storage{}
|
||||
storage["playlists"] = &handler{
|
||||
service: b.service,
|
||||
|
||||
legacyStore := newLegacyStorage(b.service)
|
||||
storage["playlists"] = legacyStore
|
||||
|
||||
// enable dual writes if a RESTOptionsGetter is provided
|
||||
if optsGetter != nil {
|
||||
store, err := newStorage(scheme, optsGetter)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
storage["playlists"] = grafanarest.NewDualWriter(legacyStore, store)
|
||||
}
|
||||
|
||||
apiGroupInfo.VersionedResourcesStorageMap[VersionID] = storage
|
||||
return &apiGroupInfo
|
||||
return &apiGroupInfo, nil
|
||||
}
|
||||
|
||||
func (b *PlaylistAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
||||
@@ -0,0 +1,40 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/generic"
|
||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
|
||||
grafanarest "github.com/grafana/grafana/pkg/services/grafana-apiserver/rest"
|
||||
)
|
||||
|
||||
var _ grafanarest.Storage = (*storage)(nil)
|
||||
|
||||
type storage struct {
|
||||
*genericregistry.Store
|
||||
}
|
||||
|
||||
func newStorage(scheme *runtime.Scheme, optsGetter generic.RESTOptionsGetter) (*storage, error) {
|
||||
strategy := grafanaregistry.NewStrategy(scheme)
|
||||
|
||||
store := &genericregistry.Store{
|
||||
NewFunc: func() runtime.Object { return &Playlist{} },
|
||||
NewListFunc: func() runtime.Object { return &PlaylistList{} },
|
||||
PredicateFunc: grafanaregistry.Matcher,
|
||||
DefaultQualifiedResource: Resource("playlists"),
|
||||
SingularQualifiedResource: Resource("playlist"),
|
||||
|
||||
CreateStrategy: strategy,
|
||||
UpdateStrategy: strategy,
|
||||
DeleteStrategy: strategy,
|
||||
|
||||
TableConvertor: rest.NewDefaultTableConvertor(Resource("playlists")),
|
||||
}
|
||||
options := &generic.StoreOptions{RESTOptions: optsGetter, AttrFunc: grafanaregistry.GetAttrs}
|
||||
if err := store.CompleteWithOptions(options); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &storage{Store: store}, nil
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package v1
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
+4
-6
@@ -1,11 +1,9 @@
|
||||
//go:build !ignore_autogenerated
|
||||
// +build !ignore_autogenerated
|
||||
|
||||
// generated by scripts/k8s/update-codegen.sh
|
||||
|
||||
// Code generated by deepcopy-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
@@ -71,17 +69,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 *legacyStorage) DeepCopyInto(out *legacyStorage) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Storage.
|
||||
func (in *handler) DeepCopy() *handler {
|
||||
func (in *legacyStorage) DeepCopy() *legacyStorage {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(handler)
|
||||
out := new(legacyStorage)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
@@ -1,130 +0,0 @@
|
||||
package v1
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"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)
|
||||
var _ rest.SingularNameProvider = (*handler)(nil)
|
||||
var _ rest.Getter = (*handler)(nil)
|
||||
var _ rest.Lister = (*handler)(nil)
|
||||
var _ rest.Storage = (*handler)(nil)
|
||||
|
||||
type handler struct {
|
||||
service playlist.Service
|
||||
}
|
||||
|
||||
func (r *handler) New() runtime.Object {
|
||||
return &Playlist{}
|
||||
}
|
||||
|
||||
func (r *handler) Destroy() {}
|
||||
|
||||
func (r *handler) NamespaceScoped() bool {
|
||||
return true // namespace == org
|
||||
}
|
||||
|
||||
func (r *handler) GetSingularName() string {
|
||||
return "playlist"
|
||||
}
|
||||
|
||||
func (r *handler) NewList() runtime.Object {
|
||||
return &PlaylistList{}
|
||||
}
|
||||
|
||||
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) {
|
||||
ns, ok := request.NamespaceFrom(ctx)
|
||||
if !ok || ns == "" {
|
||||
return nil, fmt.Errorf("namespace required")
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
}
|
||||
for _, v := range res {
|
||||
p := Playlist{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Playlist",
|
||||
APIVersion: APIVersion,
|
||||
},
|
||||
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 == "" {
|
||||
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?")
|
||||
}
|
||||
|
||||
return &Playlist{
|
||||
TypeMeta: metav1.TypeMeta{
|
||||
Kind: "Playlist",
|
||||
APIVersion: APIVersion,
|
||||
},
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: p.Uid,
|
||||
},
|
||||
Name: p.Name + "//" + p.Interval,
|
||||
}, nil
|
||||
}
|
||||
+3
-2
@@ -2,9 +2,10 @@ package apis
|
||||
|
||||
import (
|
||||
"github.com/google/wire"
|
||||
playlistv1 "github.com/grafana/grafana/pkg/apis/playlist/v1"
|
||||
|
||||
playlistsv0alpha1 "github.com/grafana/grafana/pkg/apis/playlist/v0alpha1"
|
||||
)
|
||||
|
||||
var WireSet = wire.NewSet(
|
||||
playlistv1.RegisterAPIService,
|
||||
playlistsv0alpha1.RegisterAPIService,
|
||||
)
|
||||
|
||||
Reference in New Issue
Block a user