Playlist: Implement the entire API with k8s client (#77596)
This commit is contained in:
+172
-112
@@ -16,127 +16,34 @@ import (
|
||||
internalplaylist "github.com/grafana/grafana/pkg/registry/apis/playlist"
|
||||
contextmodel "github.com/grafana/grafana/pkg/services/contexthandler/model"
|
||||
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||
grafanaapiserver "github.com/grafana/grafana/pkg/services/grafana-apiserver"
|
||||
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/playlist"
|
||||
"github.com/grafana/grafana/pkg/util/errutil/errhttp"
|
||||
"github.com/grafana/grafana/pkg/web"
|
||||
)
|
||||
|
||||
type playlistAPIHandler struct {
|
||||
SearchPlaylists []web.Handler
|
||||
GetPlaylist []web.Handler
|
||||
GetPlaylistItems []web.Handler
|
||||
DeletePlaylist []web.Handler
|
||||
UpdatePlaylist []web.Handler
|
||||
CreatePlaylist []web.Handler
|
||||
}
|
||||
|
||||
func chainHandlers(h ...web.Handler) []web.Handler {
|
||||
return h
|
||||
}
|
||||
|
||||
func (hs *HTTPServer) registerPlaylistAPI(apiRoute routing.RouteRegister) {
|
||||
handler := playlistAPIHandler{
|
||||
SearchPlaylists: chainHandlers(routing.Wrap(hs.SearchPlaylists)),
|
||||
GetPlaylist: chainHandlers(hs.validateOrgPlaylist, routing.Wrap(hs.GetPlaylist)),
|
||||
GetPlaylistItems: chainHandlers(hs.validateOrgPlaylist, routing.Wrap(hs.GetPlaylistItems)),
|
||||
DeletePlaylist: chainHandlers(middleware.ReqEditorRole, hs.validateOrgPlaylist, routing.Wrap(hs.DeletePlaylist)),
|
||||
UpdatePlaylist: chainHandlers(middleware.ReqEditorRole, hs.validateOrgPlaylist, routing.Wrap(hs.UpdatePlaylist)),
|
||||
CreatePlaylist: chainHandlers(middleware.ReqEditorRole, routing.Wrap(hs.CreatePlaylist)),
|
||||
}
|
||||
|
||||
// Alternative implementations for k8s
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagKubernetesPlaylistsAPI) {
|
||||
namespacer := request.GetNamespaceMapper(hs.Cfg)
|
||||
gvr := schema.GroupVersionResource{
|
||||
Group: internalplaylist.GroupName,
|
||||
Version: internalplaylist.VersionID,
|
||||
Resource: "playlists",
|
||||
}
|
||||
|
||||
clientGetter := func(c *contextmodel.ReqContext) (dynamic.ResourceInterface, bool) {
|
||||
dyn, err := dynamic.NewForConfig(hs.clientConfigProvider.GetDirectRestConfig(c))
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "client", err)
|
||||
return nil, false
|
||||
}
|
||||
return dyn.Resource(gvr).Namespace(namespacer(c.OrgID)), true
|
||||
}
|
||||
|
||||
errorWriter := func(c *contextmodel.ReqContext, err error) {
|
||||
//nolint:errorlint
|
||||
statusError, ok := err.(*errors.StatusError)
|
||||
if ok {
|
||||
c.JsonApiErr(int(statusError.Status().Code),
|
||||
statusError.Status().Message, err)
|
||||
return
|
||||
}
|
||||
errhttp.Write(c.Req.Context(), err, c.Resp)
|
||||
}
|
||||
|
||||
handler.SearchPlaylists = []web.Handler{func(c *contextmodel.ReqContext) {
|
||||
client, ok := clientGetter(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
out, err := client.List(c.Req.Context(), v1.ListOptions{})
|
||||
if err != nil {
|
||||
errorWriter(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := strings.ToUpper(c.Query("query"))
|
||||
playlists := []playlist.Playlist{}
|
||||
for _, item := range out.Items {
|
||||
p := internalplaylist.UnstructuredToLegacyPlaylist(item)
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
if query != "" && !strings.Contains(strings.ToUpper(p.Name), query) {
|
||||
continue // query filter
|
||||
}
|
||||
playlists = append(playlists, *p)
|
||||
}
|
||||
c.JSON(http.StatusOK, playlists)
|
||||
}}
|
||||
|
||||
handler.GetPlaylist = []web.Handler{func(c *contextmodel.ReqContext) {
|
||||
client, ok := clientGetter(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
out, err := client.Get(c.Req.Context(), uid, v1.GetOptions{})
|
||||
if err != nil {
|
||||
errorWriter(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, internalplaylist.UnstructuredToLegacyPlaylistDTO(*out))
|
||||
}}
|
||||
|
||||
handler.GetPlaylistItems = []web.Handler{func(c *contextmodel.ReqContext) {
|
||||
client, ok := clientGetter(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
out, err := client.Get(c.Req.Context(), uid, v1.GetOptions{})
|
||||
if err != nil {
|
||||
errorWriter(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, internalplaylist.UnstructuredToLegacyPlaylistDTO(*out).Items)
|
||||
}}
|
||||
}
|
||||
|
||||
// Register the actual handlers
|
||||
apiRoute.Group("/playlists", func(playlistRoute routing.RouteRegister) {
|
||||
playlistRoute.Get("/", handler.SearchPlaylists...)
|
||||
playlistRoute.Get("/:uid", handler.GetPlaylist...)
|
||||
playlistRoute.Get("/:uid/items", handler.GetPlaylistItems...)
|
||||
playlistRoute.Delete("/:uid", handler.DeletePlaylist...)
|
||||
playlistRoute.Put("/:uid", handler.UpdatePlaylist...)
|
||||
playlistRoute.Post("/", handler.CreatePlaylist...)
|
||||
if hs.Features.IsEnabled(featuremgmt.FlagKubernetesPlaylistsAPI) {
|
||||
// Use k8s client to implement legacy API
|
||||
handler := newPlaylistK8sHandler(hs)
|
||||
playlistRoute.Get("/", handler.searchPlaylists)
|
||||
playlistRoute.Get("/:uid", handler.getPlaylist)
|
||||
playlistRoute.Get("/:uid/items", handler.getPlaylistItems)
|
||||
playlistRoute.Delete("/:uid", handler.deletePlaylist)
|
||||
playlistRoute.Put("/:uid", handler.updatePlaylist)
|
||||
playlistRoute.Post("/", handler.createPlaylist)
|
||||
} else {
|
||||
// Legacy handlers
|
||||
playlistRoute.Get("/", routing.Wrap(hs.SearchPlaylists))
|
||||
playlistRoute.Get("/:uid", hs.validateOrgPlaylist, routing.Wrap(hs.GetPlaylist))
|
||||
playlistRoute.Get("/:uid/items", hs.validateOrgPlaylist, routing.Wrap(hs.GetPlaylistItems))
|
||||
playlistRoute.Delete("/:uid", middleware.ReqEditorRole, hs.validateOrgPlaylist, routing.Wrap(hs.DeletePlaylist))
|
||||
playlistRoute.Put("/:uid", middleware.ReqEditorRole, hs.validateOrgPlaylist, routing.Wrap(hs.UpdatePlaylist))
|
||||
playlistRoute.Post("/", middleware.ReqEditorRole, routing.Wrap(hs.CreatePlaylist))
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@@ -409,3 +316,156 @@ type CreatePlaylistResponse struct {
|
||||
// in: body
|
||||
Body *playlist.Playlist `json:"body"`
|
||||
}
|
||||
|
||||
type playlistK8sHandler struct {
|
||||
namespacer request.NamespaceMapper
|
||||
gvr schema.GroupVersionResource
|
||||
clientConfigProvider grafanaapiserver.DirectRestConfigProvider
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------
|
||||
// Playlist k8s wrapper functions
|
||||
//-----------------------------------------------------------------------------------------
|
||||
|
||||
func newPlaylistK8sHandler(hs *HTTPServer) *playlistK8sHandler {
|
||||
return &playlistK8sHandler{
|
||||
gvr: schema.GroupVersionResource{
|
||||
Group: internalplaylist.GroupName,
|
||||
Version: "v0alpha1",
|
||||
Resource: "playlists",
|
||||
},
|
||||
namespacer: request.GetNamespaceMapper(hs.Cfg),
|
||||
clientConfigProvider: hs.clientConfigProvider,
|
||||
}
|
||||
}
|
||||
|
||||
func (pk8s *playlistK8sHandler) searchPlaylists(c *contextmodel.ReqContext) {
|
||||
client, ok := pk8s.getClient(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
out, err := client.List(c.Req.Context(), v1.ListOptions{})
|
||||
if err != nil {
|
||||
pk8s.writeError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
query := strings.ToUpper(c.Query("query"))
|
||||
playlists := []playlist.Playlist{}
|
||||
for _, item := range out.Items {
|
||||
p := internalplaylist.UnstructuredToLegacyPlaylist(item)
|
||||
if p == nil {
|
||||
continue
|
||||
}
|
||||
if query != "" && !strings.Contains(strings.ToUpper(p.Name), query) {
|
||||
continue // query filter
|
||||
}
|
||||
playlists = append(playlists, *p)
|
||||
}
|
||||
c.JSON(http.StatusOK, playlists)
|
||||
}
|
||||
|
||||
func (pk8s *playlistK8sHandler) getPlaylist(c *contextmodel.ReqContext) {
|
||||
client, ok := pk8s.getClient(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
out, err := client.Get(c.Req.Context(), uid, v1.GetOptions{})
|
||||
if err != nil {
|
||||
pk8s.writeError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, internalplaylist.UnstructuredToLegacyPlaylistDTO(*out))
|
||||
}
|
||||
|
||||
func (pk8s *playlistK8sHandler) getPlaylistItems(c *contextmodel.ReqContext) {
|
||||
client, ok := pk8s.getClient(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
out, err := client.Get(c.Req.Context(), uid, v1.GetOptions{})
|
||||
if err != nil {
|
||||
pk8s.writeError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, internalplaylist.UnstructuredToLegacyPlaylistDTO(*out).Items)
|
||||
}
|
||||
|
||||
func (pk8s *playlistK8sHandler) deletePlaylist(c *contextmodel.ReqContext) {
|
||||
client, ok := pk8s.getClient(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
err := client.Delete(c.Req.Context(), uid, v1.DeleteOptions{})
|
||||
if err != nil {
|
||||
pk8s.writeError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, "")
|
||||
}
|
||||
|
||||
func (pk8s *playlistK8sHandler) updatePlaylist(c *contextmodel.ReqContext) {
|
||||
client, ok := pk8s.getClient(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
uid := web.Params(c.Req)[":uid"]
|
||||
cmd := playlist.UpdatePlaylistCommand{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
c.JsonApiErr(http.StatusBadRequest, "bad request data", err)
|
||||
return
|
||||
}
|
||||
obj := internalplaylist.LegacyUpdateCommandToUnstructured(cmd)
|
||||
obj.SetName(uid)
|
||||
out, err := client.Update(c.Req.Context(), &obj, v1.UpdateOptions{})
|
||||
if err != nil {
|
||||
pk8s.writeError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, internalplaylist.UnstructuredToLegacyPlaylistDTO(*out))
|
||||
}
|
||||
|
||||
func (pk8s *playlistK8sHandler) createPlaylist(c *contextmodel.ReqContext) {
|
||||
client, ok := pk8s.getClient(c)
|
||||
if !ok {
|
||||
return // error is already sent
|
||||
}
|
||||
cmd := playlist.UpdatePlaylistCommand{}
|
||||
if err := web.Bind(c.Req, &cmd); err != nil {
|
||||
c.JsonApiErr(http.StatusBadRequest, "bad request data", err)
|
||||
return
|
||||
}
|
||||
obj := internalplaylist.LegacyUpdateCommandToUnstructured(cmd)
|
||||
out, err := client.Create(c.Req.Context(), &obj, v1.CreateOptions{})
|
||||
if err != nil {
|
||||
pk8s.writeError(c, err)
|
||||
return
|
||||
}
|
||||
c.JSON(http.StatusOK, internalplaylist.UnstructuredToLegacyPlaylistDTO(*out))
|
||||
}
|
||||
|
||||
//-----------------------------------------------------------------------------------------
|
||||
// Utility functions
|
||||
//-----------------------------------------------------------------------------------------
|
||||
|
||||
func (pk8s *playlistK8sHandler) getClient(c *contextmodel.ReqContext) (dynamic.ResourceInterface, bool) {
|
||||
dyn, err := dynamic.NewForConfig(pk8s.clientConfigProvider.GetDirectRestConfig(c))
|
||||
if err != nil {
|
||||
c.JsonApiErr(500, "client", err)
|
||||
return nil, false
|
||||
}
|
||||
return dyn.Resource(pk8s.gvr).Namespace(pk8s.namespacer(c.OrgID)), true
|
||||
}
|
||||
|
||||
func (pk8s *playlistK8sHandler) writeError(c *contextmodel.ReqContext, err error) {
|
||||
//nolint:errorlint
|
||||
statusError, ok := err.(*errors.StatusError)
|
||||
if ok {
|
||||
c.JsonApiErr(int(statusError.Status().Code), statusError.Status().Message, err)
|
||||
return
|
||||
}
|
||||
errhttp.Write(c.Req.Context(), err, c.Resp)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user