mirror of https://github.com/grafana/grafana.git
				
				
				
			
		
			
				
	
	
		
			180 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
			
		
		
	
	
			180 lines
		
	
	
		
			4.8 KiB
		
	
	
	
		
			Go
		
	
	
	
| package webhooks
 | |
| 
 | |
| import (
 | |
| 	"context"
 | |
| 	"fmt"
 | |
| 	"net/http"
 | |
| 	"strings"
 | |
| 	"time"
 | |
| 
 | |
| 	apierrors "k8s.io/apimachinery/pkg/api/errors"
 | |
| 	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 | |
| 	"k8s.io/apimachinery/pkg/runtime"
 | |
| 	"k8s.io/apiserver/pkg/authorization/authorizer"
 | |
| 	"k8s.io/apiserver/pkg/endpoints/request"
 | |
| 	"k8s.io/apiserver/pkg/registry/rest"
 | |
| 	"k8s.io/kube-openapi/pkg/spec3"
 | |
| 
 | |
| 	provisioning "github.com/grafana/grafana/apps/provisioning/pkg/apis/provisioning/v0alpha1"
 | |
| 	"github.com/grafana/grafana/pkg/cmd/grafana-cli/logger"
 | |
| 	provisioningapis "github.com/grafana/grafana/pkg/registry/apis/provisioning"
 | |
| 	"github.com/grafana/grafana/pkg/storage/unified/resource"
 | |
| 	"github.com/grafana/grafana/pkg/storage/unified/resourcepb"
 | |
| )
 | |
| 
 | |
| type renderConnector struct {
 | |
| 	unified resource.ResourceClient
 | |
| 	core    *provisioningapis.APIBuilder
 | |
| }
 | |
| 
 | |
| func NewRenderConnector(unified resource.ResourceClient, core *provisioningapis.APIBuilder) *renderConnector {
 | |
| 	return &renderConnector{
 | |
| 		unified: unified,
 | |
| 		core:    core,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (*renderConnector) New() runtime.Object {
 | |
| 	return &provisioning.Repository{}
 | |
| }
 | |
| 
 | |
| func (*renderConnector) Destroy() {}
 | |
| 
 | |
| func (*renderConnector) ProducesMIMETypes(verb string) []string {
 | |
| 	return []string{"application/json"}
 | |
| }
 | |
| 
 | |
| func (c *renderConnector) ProducesObject(verb string) any {
 | |
| 	return c.New()
 | |
| }
 | |
| 
 | |
| func (*renderConnector) ConnectMethods() []string {
 | |
| 	return []string{http.MethodGet}
 | |
| }
 | |
| 
 | |
| func (*renderConnector) NewConnectOptions() (runtime.Object, bool, string) {
 | |
| 	return nil, true, ""
 | |
| }
 | |
| 
 | |
| func (c *renderConnector) Authorize(_ context.Context, a authorizer.Attributes) (decision authorizer.Decision, reason string, err error) {
 | |
| 	if a.GetResource() == provisioning.RepositoryResourceInfo.GetName() && a.GetSubresource() == "render" {
 | |
| 		return authorizer.DecisionAllow, "", nil
 | |
| 	}
 | |
| 
 | |
| 	return authorizer.DecisionNoOpinion, "", nil
 | |
| }
 | |
| 
 | |
| func (c *renderConnector) PostProcessOpenAPI(oas *spec3.OpenAPI) error {
 | |
| 	root := "/apis/" + c.core.GetGroupVersion().String() + "/"
 | |
| 	repoprefix := root + "namespaces/{namespace}/repositories/{name}"
 | |
| 
 | |
| 	delete(oas.Paths.Paths, repoprefix+"/render")
 | |
| 	sub := oas.Paths.Paths[repoprefix+"/render/{path}"]
 | |
| 	if sub != nil {
 | |
| 		sub.Get.Description = "get a rendered preview image"
 | |
| 		sub.Get.Responses = &spec3.Responses{
 | |
| 			ResponsesProps: spec3.ResponsesProps{
 | |
| 				StatusCodeResponses: map[int]*spec3.Response{
 | |
| 					200: {
 | |
| 						ResponseProps: spec3.ResponseProps{
 | |
| 							Content: map[string]*spec3.MediaType{
 | |
| 								"image/png": {},
 | |
| 							},
 | |
| 							Description: "OK",
 | |
| 						},
 | |
| 					},
 | |
| 				},
 | |
| 			},
 | |
| 		}
 | |
| 
 | |
| 		// Replace {path} with {guid} (it is a GUID, but all k8s sub-resources are called path)
 | |
| 		for _, v := range sub.Parameters {
 | |
| 			if v.Name == "path" {
 | |
| 				v.Name = "guid"
 | |
| 				v.Description = "Image GUID"
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		delete(oas.Paths.Paths, repoprefix+"/render/{path}")
 | |
| 		oas.Paths.Paths[repoprefix+"/render/{guid}"] = sub
 | |
| 	}
 | |
| 
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *renderConnector) UpdateStorage(storage map[string]rest.Storage) error {
 | |
| 	storage[provisioning.RepositoryResourceInfo.StoragePath("render")] = c
 | |
| 	return nil
 | |
| }
 | |
| 
 | |
| func (c *renderConnector) Connect(
 | |
| 	ctx context.Context,
 | |
| 	name string,
 | |
| 	opts runtime.Object,
 | |
| 	responder rest.Responder,
 | |
| ) (http.Handler, error) {
 | |
| 	namespace := request.NamespaceValue(ctx)
 | |
| 	return provisioningapis.WithTimeout(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
 | |
| 		prefix := fmt.Sprintf("/%s/render", name)
 | |
| 		idx := strings.Index(r.URL.Path, prefix)
 | |
| 		if idx == -1 {
 | |
| 			logger.Debug("failed to find a file path in the URL")
 | |
| 			responder.Error(apierrors.NewBadRequest("invalid request path"))
 | |
| 			return
 | |
| 		}
 | |
| 		blobID := strings.TrimPrefix(r.URL.Path[idx+len(prefix):], "/")
 | |
| 		if len(blobID) == 0 {
 | |
| 			responder.Error(apierrors.NewNotFound(provisioning.RepositoryResourceInfo.GroupResource(), "render"))
 | |
| 			return
 | |
| 		}
 | |
| 		if !provisioningapis.ValidUUID(blobID) {
 | |
| 			responder.Error(apierrors.NewBadRequest(fmt.Sprintf("invalid blob id: %s", blobID)))
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		rsp, err := c.unified.GetBlob(ctx, &resourcepb.GetBlobRequest{
 | |
| 			Resource: &resourcepb.ResourceKey{
 | |
| 				Namespace: namespace,
 | |
| 				Group:     provisioning.GROUP,
 | |
| 				Resource:  provisioning.RepositoryResourceInfo.GroupResource().Resource,
 | |
| 				Name:      name,
 | |
| 			},
 | |
| 			MustProxyBytes: true,
 | |
| 			Uid:            blobID,
 | |
| 		})
 | |
| 		if err != nil {
 | |
| 			responder.Error(err)
 | |
| 			return
 | |
| 		}
 | |
| 		if rsp.Error != nil {
 | |
| 			responder.Error(resource.GetError(rsp.Error))
 | |
| 			return
 | |
| 		}
 | |
| 
 | |
| 		if len(rsp.Value) > 0 {
 | |
| 			if rsp.ContentType != "" {
 | |
| 				w.Header().Add("Content-Type", rsp.ContentType)
 | |
| 			}
 | |
| 			_, err = w.Write(rsp.Value)
 | |
| 			if err != nil {
 | |
| 				responder.Error(err)
 | |
| 				return
 | |
| 			}
 | |
| 		} else {
 | |
| 			responder.Error(&apierrors.StatusError{
 | |
| 				ErrStatus: v1.Status{
 | |
| 					Code:    http.StatusNoContent,
 | |
| 					Message: "empty body",
 | |
| 				},
 | |
| 			})
 | |
| 		}
 | |
| 	}), 20*time.Second), nil
 | |
| }
 | |
| 
 | |
| var (
 | |
| 	_ rest.Connecter       = (*renderConnector)(nil)
 | |
| 	_ rest.Storage         = (*renderConnector)(nil)
 | |
| 	_ rest.StorageMetadata = (*renderConnector)(nil)
 | |
| )
 |