Playlist: Implement the entire API with k8s client (#77596)

This commit is contained in:
Ryan McKinley
2023-11-03 09:25:29 -07:00
committed by GitHub
parent 224279fe0e
commit 549787d4f9
4 changed files with 254 additions and 140 deletions
+172 -112
View File
@@ -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)
}