mirror of
https://github.com/grafana/grafana.git
synced 2025-07-29 18:22:24 +08:00
227 lines
6.6 KiB
Go
227 lines
6.6 KiB
Go
package builder
|
|
|
|
import (
|
|
"maps"
|
|
"strings"
|
|
|
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
|
openapi "k8s.io/kube-openapi/pkg/common"
|
|
"k8s.io/kube-openapi/pkg/spec3"
|
|
spec "k8s.io/kube-openapi/pkg/validation/spec"
|
|
|
|
data "github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1"
|
|
secret "github.com/grafana/grafana/apps/secret/pkg/apis/secret/v1beta1"
|
|
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
|
)
|
|
|
|
// This should eventually live in grafana-app-sdk
|
|
func GetOpenAPIDefinitions(builders []APIGroupBuilder, additionalGetters ...openapi.GetOpenAPIDefinitions) openapi.GetOpenAPIDefinitions {
|
|
return func(ref openapi.ReferenceCallback) map[string]openapi.OpenAPIDefinition {
|
|
defs := common.GetOpenAPIDefinitions(ref) // common grafana apis
|
|
maps.Copy(defs, data.GetOpenAPIDefinitions(ref))
|
|
maps.Copy(defs, secret.GetOpenAPIDefinitions(ref)) // Expose secret reference to all resources
|
|
|
|
for _, getter := range additionalGetters {
|
|
if getter != nil {
|
|
maps.Copy(defs, getter(ref))
|
|
}
|
|
}
|
|
|
|
// TODO: add timerange to upstream SDK setup
|
|
maps.Copy(defs, map[string]openapi.OpenAPIDefinition{
|
|
"github.com/grafana/grafana-plugin-sdk-go/experimental/apis/data/v0alpha1.TimeRange": {
|
|
Schema: spec.Schema{
|
|
SchemaProps: spec.SchemaProps{
|
|
Type: []string{"object"},
|
|
AdditionalProperties: &spec.SchemaOrBool{Allows: true},
|
|
},
|
|
},
|
|
},
|
|
})
|
|
for _, b := range builders {
|
|
g := b.GetOpenAPIDefinitions()
|
|
if g != nil {
|
|
out := g(ref)
|
|
maps.Copy(defs, out)
|
|
}
|
|
}
|
|
return defs
|
|
}
|
|
}
|
|
|
|
func addBuilderRoutes(
|
|
targetGroupVersion schema.GroupVersion,
|
|
openAPISpec *spec3.OpenAPI,
|
|
apiGroupBuilders []APIGroupBuilder,
|
|
) (*spec3.OpenAPI, error) {
|
|
for _, apiGroupBuilder := range apiGroupBuilders {
|
|
// Optionally include raw http handlers for all builders
|
|
for _, gv := range GetGroupVersions(apiGroupBuilder) {
|
|
if gv != targetGroupVersion {
|
|
continue // Only add routes for the target group version
|
|
}
|
|
provider, ok := apiGroupBuilder.(APIGroupRouteProvider)
|
|
if ok && provider != nil {
|
|
routes := provider.GetAPIRoutes(gv)
|
|
if routes != nil {
|
|
for _, route := range routes.Root {
|
|
openAPISpec.Paths.Paths["/apis/"+gv.String()+"/"+route.Path] = &spec3.Path{
|
|
PathProps: *route.Spec,
|
|
}
|
|
}
|
|
|
|
for _, route := range routes.Namespace {
|
|
openAPISpec.Paths.Paths["/apis/"+gv.String()+"/namespaces/{namespace}/"+route.Path] = &spec3.Path{
|
|
PathProps: *route.Spec,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
// Support direct manipulation of API results
|
|
processor, ok := apiGroupBuilder.(OpenAPIPostProcessor)
|
|
if ok {
|
|
return processor.PostProcessOpenAPI(openAPISpec)
|
|
}
|
|
}
|
|
}
|
|
return openAPISpec, nil
|
|
}
|
|
|
|
// Modify the OpenAPI spec to include the additional routes.
|
|
// nolint:gocyclo
|
|
func getOpenAPIPostProcessor(version string, builders []APIGroupBuilder, gvs []schema.GroupVersion) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
|
return func(s *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
|
if s.Paths == nil {
|
|
return s, nil
|
|
}
|
|
|
|
for _, gv := range gvs {
|
|
prefix := "/apis/" + gv.String() + "/"
|
|
if s.Paths.Paths[prefix] != nil {
|
|
copy := spec3.OpenAPI{
|
|
Version: s.Version,
|
|
Info: &spec.Info{
|
|
InfoProps: spec.InfoProps{
|
|
Title: gv.String(),
|
|
Version: version,
|
|
},
|
|
},
|
|
Components: s.Components,
|
|
ExternalDocs: s.ExternalDocs,
|
|
Servers: s.Servers,
|
|
Paths: s.Paths,
|
|
}
|
|
|
|
for k, v := range copy.Paths.Paths {
|
|
if k == prefix {
|
|
continue // API discovery
|
|
}
|
|
|
|
// Remove the deprecated watch URL -- can use list with ?watch=true
|
|
if strings.HasPrefix(k, prefix+"watch/") {
|
|
delete(copy.Paths.Paths, k)
|
|
continue
|
|
}
|
|
|
|
// Remove the "for all namespaces" global routes from OpenAPI (v3)
|
|
if !strings.HasPrefix(k, prefix+"namespaces/") {
|
|
delete(copy.Paths.Paths, k)
|
|
continue
|
|
}
|
|
|
|
// Delete has all parameters in the query string already
|
|
if v.Delete != nil {
|
|
action, ok := v.Delete.Extensions.GetString("x-kubernetes-action")
|
|
if ok && (action == "deletecollection" || action == "delete") {
|
|
v.Delete.RequestBody = nil // duplicates all the parameters
|
|
}
|
|
}
|
|
|
|
// Replace any */* media types with json+yaml (protobuf?)
|
|
ops := []*spec3.Operation{v.Delete, v.Put, v.Post}
|
|
for _, op := range ops {
|
|
if op == nil || op.RequestBody == nil || len(op.RequestBody.Content) != 1 {
|
|
continue
|
|
}
|
|
content, ok := op.RequestBody.Content["*/*"]
|
|
if ok {
|
|
op.RequestBody.Content = map[string]*spec3.MediaType{
|
|
"application/json": content,
|
|
"application/yaml": content,
|
|
"application/vnd.kubernetes.protobuf": content,
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
sub := copy.Paths.Paths[prefix]
|
|
if sub != nil && sub.Get != nil {
|
|
sub.Get.Tags = []string{"API Discovery"}
|
|
sub.Get.Description = "Describe the available kubernetes resources"
|
|
}
|
|
|
|
// Remove the growing list of kinds
|
|
for k, v := range copy.Components.Schemas {
|
|
if v.Extensions == nil {
|
|
continue
|
|
}
|
|
if strings.HasPrefix(k, "io.k8s.apimachinery.pkg.apis.meta.v1") {
|
|
delete(v.Extensions, "x-kubernetes-group-version-kind") // a growing list of everything
|
|
continue
|
|
}
|
|
|
|
// Remove the internal annotations
|
|
val, ok := v.Extensions["x-kubernetes-group-version-kind"]
|
|
if ok {
|
|
gvks, ok := val.([]any)
|
|
if ok {
|
|
keep := make([]map[string]any, 0, len(gvks))
|
|
for _, val := range gvks {
|
|
gvk, ok := val.(map[string]any)
|
|
if ok && gvk["version"] == "__internal" {
|
|
continue
|
|
}
|
|
keep = append(keep, gvk)
|
|
}
|
|
v.Extensions["x-kubernetes-group-version-kind"] = keep
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make the sub-resources (connect) share the same tags as the main resource
|
|
for path, spec := range copy.Paths.Paths {
|
|
idx := strings.LastIndex(path, "{name}/")
|
|
if idx > 0 {
|
|
parent := copy.Paths.Paths[path[:idx+6]]
|
|
if parent != nil && parent.Get != nil {
|
|
for _, op := range GetPathOperations(spec) {
|
|
if op != nil && op.Extensions != nil {
|
|
action, ok := op.Extensions.GetString("x-kubernetes-action")
|
|
if ok && action == "connect" {
|
|
op.Tags = parent.Get.Tags
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return addBuilderRoutes(gv, ©, builders)
|
|
}
|
|
}
|
|
return s, nil
|
|
}
|
|
}
|
|
|
|
func GetPathOperations(path *spec3.Path) []*spec3.Operation {
|
|
return []*spec3.Operation{
|
|
path.Get,
|
|
path.Head,
|
|
path.Delete,
|
|
path.Patch,
|
|
path.Post,
|
|
path.Put,
|
|
path.Trace,
|
|
path.Options,
|
|
}
|
|
}
|