The open and composable observability and data visualization platform. Visualize metrics, logs, and traces from multiple sources like Prometheus, Loki, Elasticsearch, InfluxDB, Postgres and many more.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 
grafana/pkg/registry/apis/provisioning/routes.go

187 lines
6.0 KiB

package provisioning
import (
"encoding/json"
"fmt"
"net/http"
"time"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/kube-openapi/pkg/spec3"
"k8s.io/kube-openapi/pkg/validation/spec"
authlib "github.com/grafana/authlib/types"
provisioning "github.com/grafana/grafana/pkg/apis/provisioning/v0alpha1"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/storage/legacysql/dualwrite"
"github.com/grafana/grafana/pkg/util/errhttp"
)
// TODO: Move the specific logic to the connector so that we don't have logic all over the place.
// GetAPIRoutes implements the direct HTTP handlers that bypass k8s
func (b *APIBuilder) GetAPIRoutes(gv schema.GroupVersion) *builder.APIRoutes {
return &builder.APIRoutes{
Namespace: []builder.APIRouteHandler{
{
Path: "stats",
Spec: &spec3.PathProps{
Get: &spec3.Operation{
OperationProps: spec3.OperationProps{
OperationId: "getResourceStats", // used for RTK client
Tags: []string{"Provisioning", "Repository"}, // includes stats for repositores and provisiong in general
Description: "Get resource stats for this namespace",
Parameters: []*spec3.Parameter{
{
ParameterProps: spec3.ParameterProps{
Name: "namespace",
In: "path",
Required: true,
Example: "default",
Description: "workspace",
Schema: spec.StringProperty(),
},
},
},
Responses: &spec3.Responses{
ResponsesProps: spec3.ResponsesProps{
StatusCodeResponses: map[int]*spec3.Response{
200: {
ResponseProps: spec3.ResponseProps{
Content: map[string]*spec3.MediaType{
"application/json": {
MediaTypeProps: spec3.MediaTypeProps{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.ResourceStats"),
},
},
},
},
},
},
},
},
},
},
},
},
},
Handler: WithTimeoutFunc(b.handleStats, 30*time.Second),
},
{
Path: "settings",
Spec: &spec3.PathProps{
Get: &spec3.Operation{
OperationProps: spec3.OperationProps{
OperationId: "getFrontendSettings", // used for RTK client
// includes stats for repositores and provisiong in general
// This must include "Repository" so that the RTK client will invalidate when things are deleted
Tags: []string{"Provisioning", "Repository"},
Description: "Get the frontend settings for this namespace",
Parameters: []*spec3.Parameter{
{
ParameterProps: spec3.ParameterProps{
Name: "namespace",
In: "path",
Required: true,
Example: "default",
Description: "workspace",
Schema: spec.StringProperty(),
},
},
},
Responses: &spec3.Responses{
ResponsesProps: spec3.ResponsesProps{
StatusCodeResponses: map[int]*spec3.Response{
200: {
ResponseProps: spec3.ResponseProps{
Content: map[string]*spec3.MediaType{
"application/json": {
MediaTypeProps: spec3.MediaTypeProps{
Schema: &spec.Schema{
SchemaProps: spec.SchemaProps{
Ref: spec.MustCreateRef("#/components/schemas/com.github.grafana.grafana.pkg.apis.provisioning.v0alpha1.RepositoryViewList"),
},
},
},
},
},
},
},
},
},
},
},
},
},
Handler: WithTimeoutFunc(b.handleSettings, 30*time.Second),
},
},
}
}
// TODO: why didn't we create a connector as we did before or have a separate file?
func (b *APIBuilder) handleStats(w http.ResponseWriter, r *http.Request) {
u, ok := authlib.AuthInfoFrom(r.Context())
if !ok {
w.WriteHeader(400)
_, _ = w.Write([]byte("expected user"))
return
}
// TODO: check if lister could list too many repositories or resources
stats, err := b.resourceLister.Stats(r.Context(), u.GetNamespace(), "")
if err != nil {
errhttp.Write(r.Context(), err, w)
return
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(stats)
}
// TODO: why didn't we create a connector as we did before or have a separate file?
// TODO: is there a better way to provide a filtered view of the repositories to the frontend?
func (b *APIBuilder) handleSettings(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
u, ok := authlib.AuthInfoFrom(ctx)
if !ok {
errhttp.Write(ctx, fmt.Errorf("expected user"), w)
return
}
// TODO: check if lister could list too many repositories or resources
all, err := b.repositoryLister.Repositories(u.GetNamespace()).List(labels.Everything())
if err != nil {
errhttp.Write(r.Context(), err, w)
return
}
availableTypes := []provisioning.RepositoryType{}
for t := range b.availableRepositoryTypes {
availableTypes = append(availableTypes, t)
}
settings := provisioning.RepositoryViewList{
Items: make([]provisioning.RepositoryView, len(all)),
// FIXME: this shouldn't be here in provisioning but at the dual writer or something about the storage
LegacyStorage: dualwrite.IsReadingLegacyDashboardsAndFolders(ctx, b.storageStatus),
AvailableRepositoryTypes: availableTypes,
}
for i, val := range all {
branch := ""
if val.Spec.GitHub != nil {
branch = val.Spec.GitHub.Branch
}
settings.Items[i] = provisioning.RepositoryView{
Name: val.Name,
Title: val.Spec.Title,
Type: val.Spec.Type,
Target: val.Spec.Sync.Target,
Branch: branch,
Workflows: val.Spec.Workflows,
}
}
w.Header().Set("Content-Type", "application/json")
_ = json.NewEncoder(w).Encode(settings)
}