Scopes: Add a /find query endpoint (#87457)
--------- Co-authored-by: Ryan McKinley <ryantxu@gmail.com>
This commit is contained in:
co-authored by
Ryan McKinley
parent
01d28e01d2
commit
6e4d35e1ee
@@ -57,6 +57,7 @@ func AddKnownTypes(gv schema.GroupVersion, scheme *runtime.Scheme) error {
|
|||||||
&ScopeDashboardBindingList{},
|
&ScopeDashboardBindingList{},
|
||||||
&ScopeNode{},
|
&ScopeNode{},
|
||||||
&ScopeNodeList{},
|
&ScopeNodeList{},
|
||||||
|
&TreeResults{},
|
||||||
)
|
)
|
||||||
//metav1.AddToGroupVersion(scheme, gv)
|
//metav1.AddToGroupVersion(scheme, gv)
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -82,15 +82,37 @@ type ScopeNode struct {
|
|||||||
Spec ScopeNodeSpec `json:"spec,omitempty"`
|
Spec ScopeNodeSpec `json:"spec,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Type of the item.
|
||||||
|
// +enum
|
||||||
|
type NodeType string
|
||||||
|
|
||||||
|
// Defines values for ItemType.
|
||||||
|
const (
|
||||||
|
NodeTypeContainer NodeType = "container"
|
||||||
|
NodeTypeLeaf NodeType = "leaf"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Type of the item.
|
||||||
|
// +enum
|
||||||
|
type LinkType string
|
||||||
|
|
||||||
|
// Defines values for ItemType.
|
||||||
|
const (
|
||||||
|
LinkTypeScope LinkType = "scope"
|
||||||
|
)
|
||||||
|
|
||||||
type ScopeNodeSpec struct {
|
type ScopeNodeSpec struct {
|
||||||
//+optional
|
//+optional
|
||||||
ParentName *string `json:"parentName"`
|
ParentName string `json:"parentName,omitempty"`
|
||||||
Title string `json:"title"`
|
|
||||||
Description string `json:"description"`
|
NodeType NodeType `json:"nodeType"` // container | leaf
|
||||||
IsLeaf bool `json:"isLeaf"`
|
|
||||||
IsSelectable bool `json:"isSelectable"`
|
Title string `json:"title"`
|
||||||
LeafType string `json:"leafType"`
|
Description string `json:"description,omitempty"`
|
||||||
LeafName string `json:"leafName"`
|
|
||||||
|
LinkType LinkType `json:"linkType,omitempty"` // scope (later more things)
|
||||||
|
LinkID string `json:"linkID,omitempty"` // the k8s name
|
||||||
|
// ?? should this be a slice of links
|
||||||
}
|
}
|
||||||
|
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
@@ -100,3 +122,22 @@ type ScopeNodeList struct {
|
|||||||
|
|
||||||
Items []ScopeNode `json:"items,omitempty"`
|
Items []ScopeNode `json:"items,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
type TreeResults struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Items []TreeItem `json:"items,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type TreeItem struct {
|
||||||
|
NodeID string `json:"nodeId,omitempty"`
|
||||||
|
NodeType NodeType `json:"nodeType"` // container | leaf
|
||||||
|
|
||||||
|
Title string `json:"title"`
|
||||||
|
Description string `json:"description,omitempty"`
|
||||||
|
|
||||||
|
LinkType LinkType `json:"linkType,omitempty"` // scope (later more things)
|
||||||
|
LinkID string `json:"linkID,omitempty"` // the k8s name
|
||||||
|
}
|
||||||
|
|||||||
@@ -168,7 +168,7 @@ func (in *ScopeNode) DeepCopyInto(out *ScopeNode) {
|
|||||||
*out = *in
|
*out = *in
|
||||||
out.TypeMeta = in.TypeMeta
|
out.TypeMeta = in.TypeMeta
|
||||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
in.Spec.DeepCopyInto(&out.Spec)
|
out.Spec = in.Spec
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -226,11 +226,6 @@ func (in *ScopeNodeList) DeepCopyObject() runtime.Object {
|
|||||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *ScopeNodeSpec) DeepCopyInto(out *ScopeNodeSpec) {
|
func (in *ScopeNodeSpec) DeepCopyInto(out *ScopeNodeSpec) {
|
||||||
*out = *in
|
*out = *in
|
||||||
if in.ParentName != nil {
|
|
||||||
in, out := &in.ParentName, &out.ParentName
|
|
||||||
*out = new(string)
|
|
||||||
**out = **in
|
|
||||||
}
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -264,3 +259,50 @@ func (in *ScopeSpec) DeepCopy() *ScopeSpec {
|
|||||||
in.DeepCopyInto(out)
|
in.DeepCopyInto(out)
|
||||||
return out
|
return out
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TreeItem) DeepCopyInto(out *TreeItem) {
|
||||||
|
*out = *in
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TreeItem.
|
||||||
|
func (in *TreeItem) DeepCopy() *TreeItem {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TreeItem)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *TreeResults) DeepCopyInto(out *TreeResults) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]TreeItem, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new TreeResults.
|
||||||
|
func (in *TreeResults) DeepCopy() *TreeResults {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(TreeResults)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *TreeResults) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -26,6 +26,8 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
|||||||
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeNodeList": schema_pkg_apis_scope_v0alpha1_ScopeNodeList(ref),
|
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeNodeList": schema_pkg_apis_scope_v0alpha1_ScopeNodeList(ref),
|
||||||
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeNodeSpec": schema_pkg_apis_scope_v0alpha1_ScopeNodeSpec(ref),
|
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeNodeSpec": schema_pkg_apis_scope_v0alpha1_ScopeNodeSpec(ref),
|
||||||
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeSpec": schema_pkg_apis_scope_v0alpha1_ScopeSpec(ref),
|
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeSpec": schema_pkg_apis_scope_v0alpha1_ScopeSpec(ref),
|
||||||
|
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.TreeItem": schema_pkg_apis_scope_v0alpha1_TreeItem(ref),
|
||||||
|
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.TreeResults": schema_pkg_apis_scope_v0alpha1_TreeResults(ref),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -365,6 +367,15 @@ func schema_pkg_apis_scope_v0alpha1_ScopeNodeSpec(ref common.ReferenceCallback)
|
|||||||
Format: "",
|
Format: "",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
"nodeType": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Possible enum values:\n - `\"container\"`\n - `\"leaf\"`",
|
||||||
|
Default: "",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
Enum: []interface{}{"container", "leaf"},
|
||||||
|
},
|
||||||
|
},
|
||||||
"title": {
|
"title": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: "",
|
Default: "",
|
||||||
@@ -374,41 +385,27 @@ func schema_pkg_apis_scope_v0alpha1_ScopeNodeSpec(ref common.ReferenceCallback)
|
|||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: "",
|
Type: []string{"string"},
|
||||||
Type: []string{"string"},
|
Format: "",
|
||||||
Format: "",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"isLeaf": {
|
"linkType": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: false,
|
Description: "Possible enum values:\n - `\"scope\"`",
|
||||||
Type: []string{"boolean"},
|
Type: []string{"string"},
|
||||||
Format: "",
|
Format: "",
|
||||||
|
Enum: []interface{}{"scope"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
"isSelectable": {
|
"linkID": {
|
||||||
SchemaProps: spec.SchemaProps{
|
SchemaProps: spec.SchemaProps{
|
||||||
Default: false,
|
Description: "scope (later more things)",
|
||||||
Type: []string{"boolean"},
|
Type: []string{"string"},
|
||||||
Format: "",
|
Format: "",
|
||||||
},
|
|
||||||
},
|
|
||||||
"leafType": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Default: "",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"leafName": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Default: "",
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
Required: []string{"title", "description", "isLeaf", "isSelectable", "leafType", "leafName"},
|
Required: []string{"nodeType", "title"},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
@@ -474,3 +471,106 @@ func schema_pkg_apis_scope_v0alpha1_ScopeSpec(ref common.ReferenceCallback) comm
|
|||||||
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeFilter"},
|
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.ScopeFilter"},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func schema_pkg_apis_scope_v0alpha1_TreeItem(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"object"},
|
||||||
|
Properties: map[string]spec.Schema{
|
||||||
|
"nodeId": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"nodeType": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Possible enum values:\n - `\"container\"`\n - `\"leaf\"`",
|
||||||
|
Default: "",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
Enum: []interface{}{"container", "leaf"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"title": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: "",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"description": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"linkType": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Possible enum values:\n - `\"scope\"`",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
Enum: []interface{}{"scope"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"linkID": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "scope (later more things)",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Required: []string{"nodeType", "title"},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func schema_pkg_apis_scope_v0alpha1_TreeResults(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"object"},
|
||||||
|
Properties: map[string]spec.Schema{
|
||||||
|
"kind": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Kind is a string value representing the REST resource this object represents. Servers may infer this from the endpoint the client submits requests to. Cannot be updated. In CamelCase. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"apiVersion": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "APIVersion defines the versioned schema of this representation of an object. Servers should convert recognized schemas to the latest internal value, and may reject unrecognized values. More info: https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: map[string]interface{}{},
|
||||||
|
Ref: ref("k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"items": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"array"},
|
||||||
|
Items: &spec.SchemaOrArray{
|
||||||
|
Schema: &spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Default: map[string]interface{}{},
|
||||||
|
Ref: ref("github.com/grafana/grafana/pkg/apis/scope/v0alpha1.TreeItem"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"github.com/grafana/grafana/pkg/apis/scope/v0alpha1.TreeItem", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1 @@
|
|||||||
|
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/scope/v0alpha1,TreeItem,NodeID
|
||||||
@@ -3,6 +3,7 @@ package builder
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"regexp"
|
||||||
goruntime "runtime"
|
goruntime "runtime"
|
||||||
"runtime/debug"
|
"runtime/debug"
|
||||||
"strconv"
|
"strconv"
|
||||||
@@ -25,6 +26,16 @@ import (
|
|||||||
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
|
"github.com/grafana/grafana/pkg/apiserver/endpoints/filters"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var pathRewriters = []filters.PathRewriter{
|
||||||
|
{
|
||||||
|
// TODO: this is a temporary hack to make rest.Connecter work with resource level routes
|
||||||
|
Pattern: regexp.MustCompile(`(/apis/scope.grafana.app/v0alpha1/namespaces/.*/find$)`),
|
||||||
|
ReplaceFunc: func(matches []string) string {
|
||||||
|
return matches[1] + "/name" // connector requires a name
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
func SetupConfig(
|
func SetupConfig(
|
||||||
scheme *runtime.Scheme,
|
scheme *runtime.Scheme,
|
||||||
serverConfig *genericapiserver.RecommendedConfig,
|
serverConfig *genericapiserver.RecommendedConfig,
|
||||||
@@ -76,6 +87,7 @@ func SetupConfig(
|
|||||||
|
|
||||||
handler := genericapiserver.DefaultBuildHandlerChain(requestHandler, c)
|
handler := genericapiserver.DefaultBuildHandlerChain(requestHandler, c)
|
||||||
handler = filters.WithAcceptHeader(handler)
|
handler = filters.WithAcceptHeader(handler)
|
||||||
|
handler = filters.WithPathRewriters(handler, pathRewriters)
|
||||||
handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI")
|
handler = k8stracing.WithTracing(handler, serverConfig.TracerProvider, "KubernetesAPI")
|
||||||
|
|
||||||
return handler
|
return handler
|
||||||
|
|||||||
@@ -0,0 +1,99 @@
|
|||||||
|
package scope
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/api/errors"
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
|
scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1"
|
||||||
|
)
|
||||||
|
|
||||||
|
type findREST struct {
|
||||||
|
scopeNodeStorage *storage
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ rest.Storage = (*findREST)(nil)
|
||||||
|
_ rest.SingularNameProvider = (*findREST)(nil)
|
||||||
|
_ rest.Connecter = (*findREST)(nil)
|
||||||
|
_ rest.Scoper = (*findREST)(nil)
|
||||||
|
_ rest.StorageMetadata = (*findREST)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
func (r *findREST) New() runtime.Object {
|
||||||
|
// This is added as the "ResponseType" regarless what ProducesObject() says :)
|
||||||
|
return &scope.TreeResults{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *findREST) Destroy() {}
|
||||||
|
|
||||||
|
func (r *findREST) NamespaceScoped() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *findREST) GetSingularName() string {
|
||||||
|
return "TreeResult" // Used for the
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *findREST) ProducesMIMETypes(verb string) []string {
|
||||||
|
return []string{"application/json"} // and parquet!
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *findREST) ProducesObject(verb string) interface{} {
|
||||||
|
return &scope.TreeResults{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *findREST) ConnectMethods() []string {
|
||||||
|
return []string{"GET"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *findREST) NewConnectOptions() (runtime.Object, bool, string) {
|
||||||
|
return nil, false, "" // true means you can use the trailing path as a variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *findREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
|
||||||
|
// See: /pkg/apiserver/builder/helper.go#L34
|
||||||
|
// The name is set with a rewriter hack
|
||||||
|
if name != "name" {
|
||||||
|
return nil, errors.NewNotFound(schema.GroupResource{}, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
parent := req.URL.Query().Get("parent")
|
||||||
|
results := &scope.TreeResults{}
|
||||||
|
|
||||||
|
raw, err := r.scopeNodeStorage.List(ctx, &internalversion.ListOptions{
|
||||||
|
Limit: 1000,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
responder.Error(err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
all, ok := raw.(*scope.ScopeNodeList)
|
||||||
|
if !ok {
|
||||||
|
responder.Error(fmt.Errorf("expected ScopeNodeList"))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, item := range all.Items {
|
||||||
|
if parent != item.Spec.ParentName && parent != "" {
|
||||||
|
continue // Someday this will have an index in raw storage on parentName
|
||||||
|
}
|
||||||
|
results.Items = append(results.Items, scope.TreeItem{
|
||||||
|
NodeID: item.Name,
|
||||||
|
NodeType: item.Spec.NodeType,
|
||||||
|
Title: item.Spec.Title,
|
||||||
|
Description: item.Spec.Description,
|
||||||
|
LinkType: item.Spec.LinkType,
|
||||||
|
LinkID: item.Spec.LinkID,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
responder.Object(200, results)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
genericapiserver "k8s.io/apiserver/pkg/server"
|
genericapiserver "k8s.io/apiserver/pkg/server"
|
||||||
"k8s.io/kube-openapi/pkg/common"
|
"k8s.io/kube-openapi/pkg/common"
|
||||||
|
"k8s.io/kube-openapi/pkg/spec3"
|
||||||
|
|
||||||
scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1"
|
scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1"
|
||||||
"github.com/grafana/grafana/pkg/apiserver/builder"
|
"github.com/grafana/grafana/pkg/apiserver/builder"
|
||||||
@@ -140,6 +141,11 @@ func (b *ScopeAPIBuilder) GetAPIGroupInfo(
|
|||||||
}
|
}
|
||||||
storage[scopeNodeResourceInfo.StoragePath()] = scopeNodeStorage
|
storage[scopeNodeResourceInfo.StoragePath()] = scopeNodeStorage
|
||||||
|
|
||||||
|
// Adds a rest.Connector
|
||||||
|
// NOTE! the server has a hardcoded rewrite filter that fills in a name
|
||||||
|
// so the standard k8s plumbing continues to work
|
||||||
|
storage["find"] = &findREST{scopeNodeStorage: scopeNodeStorage}
|
||||||
|
|
||||||
apiGroupInfo.VersionedResourcesStorageMap[scope.VERSION] = storage
|
apiGroupInfo.VersionedResourcesStorageMap[scope.VERSION] = storage
|
||||||
return &apiGroupInfo, nil
|
return &apiGroupInfo, nil
|
||||||
}
|
}
|
||||||
@@ -152,3 +158,46 @@ func (b *ScopeAPIBuilder) GetOpenAPIDefinitions() common.GetOpenAPIDefinitions {
|
|||||||
func (b *ScopeAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
|
func (b *ScopeAPIBuilder) GetAPIRoutes() *builder.APIRoutes {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *ScopeAPIBuilder) PostProcessOpenAPI(oas *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||||||
|
// The plugin description
|
||||||
|
oas.Info.Description = "Grafana scopes"
|
||||||
|
|
||||||
|
// The root api URL
|
||||||
|
root := "/apis/" + b.GetGroupVersion().String() + "/"
|
||||||
|
|
||||||
|
// Add query parameters to the rest.Connector
|
||||||
|
sub := oas.Paths.Paths[root+"namespaces/{namespace}/find/{name}"]
|
||||||
|
if sub != nil && sub.Get != nil {
|
||||||
|
sub.Parameters = []*spec3.Parameter{
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "namespace",
|
||||||
|
In: "path",
|
||||||
|
Description: "object name and auth scope, such as for teams and projects",
|
||||||
|
Example: "default",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
sub.Get.Description = "Navigate the scopes tree"
|
||||||
|
sub.Get.Parameters = []*spec3.Parameter{
|
||||||
|
{
|
||||||
|
ParameterProps: spec3.ParameterProps{
|
||||||
|
Name: "parent",
|
||||||
|
In: "query",
|
||||||
|
Description: "The parent scope node",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
delete(oas.Paths.Paths, root+"namespaces/{namespace}/find/{name}")
|
||||||
|
oas.Paths.Paths[root+"namespaces/{namespace}/find"] = sub
|
||||||
|
}
|
||||||
|
|
||||||
|
// The root API discovery list
|
||||||
|
sub = oas.Paths.Paths[root]
|
||||||
|
if sub != nil && sub.Get != nil {
|
||||||
|
sub.Get.Tags = []string{"API Discovery"} // sorts first in the list
|
||||||
|
}
|
||||||
|
return oas, nil
|
||||||
|
}
|
||||||
|
|||||||
@@ -10,12 +10,12 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
"k8s.io/apiserver/pkg/registry/generic"
|
"k8s.io/apiserver/pkg/registry/generic"
|
||||||
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
|
apistore "k8s.io/apiserver/pkg/storage"
|
||||||
|
|
||||||
scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1"
|
scope "github.com/grafana/grafana/pkg/apis/scope/v0alpha1"
|
||||||
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
grafanaregistry "github.com/grafana/grafana/pkg/apiserver/registry/generic"
|
||||||
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
|
||||||
"github.com/grafana/grafana/pkg/services/apiserver/utils"
|
"github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||||
apistore "k8s.io/apiserver/pkg/storage"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
var _ grafanarest.Storage = (*storage)(nil)
|
var _ grafanarest.Storage = (*storage)(nil)
|
||||||
@@ -176,8 +176,8 @@ func SelectableScopeDashboardBindingFields(obj *scope.ScopeDashboardBinding) fie
|
|||||||
func SelectableScopeNodeFields(obj *scope.ScopeNode) fields.Set {
|
func SelectableScopeNodeFields(obj *scope.ScopeNode) fields.Set {
|
||||||
parentName := ""
|
parentName := ""
|
||||||
|
|
||||||
if obj.Spec.ParentName != nil {
|
if obj != nil {
|
||||||
parentName = *obj.Spec.ParentName
|
parentName = obj.Spec.ParentName
|
||||||
}
|
}
|
||||||
|
|
||||||
return generic.MergeFieldsSets(generic.ObjectMetaFieldsSet(&obj.ObjectMeta, false), fields.Set{
|
return generic.MergeFieldsSets(generic.ObjectMetaFieldsSet(&obj.ObjectMeta, false), fields.Set{
|
||||||
|
|||||||
@@ -40,44 +40,54 @@ func TestIntegrationScopes(t *testing.T) {
|
|||||||
|
|
||||||
v1Disco, err := json.MarshalIndent(resources, "", " ")
|
v1Disco, err := json.MarshalIndent(resources, "", " ")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
//fmt.Printf("%s", string(v1Disco))
|
||||||
|
|
||||||
require.JSONEq(t, `{
|
require.JSONEq(t, `{
|
||||||
"kind": "APIResourceList",
|
"kind": "APIResourceList",
|
||||||
"apiVersion": "v1",
|
"apiVersion": "v1",
|
||||||
"groupVersion": "scope.grafana.app/v0alpha1",
|
"groupVersion": "scope.grafana.app/v0alpha1",
|
||||||
"resources": [
|
"resources": [
|
||||||
{
|
{
|
||||||
"name": "scopedashboardbindings",
|
"name": "find",
|
||||||
"singularName": "scopedashboardbinding",
|
"singularName": "TreeResult",
|
||||||
"namespaced": true,
|
"namespaced": true,
|
||||||
"kind": "ScopeDashboardBinding",
|
"kind": "TreeResults",
|
||||||
"verbs": [
|
"verbs": [
|
||||||
"create",
|
"get"
|
||||||
"delete",
|
]
|
||||||
"deletecollection",
|
},
|
||||||
"get",
|
{
|
||||||
"list",
|
"name": "scopedashboardbindings",
|
||||||
"patch",
|
"singularName": "scopedashboardbinding",
|
||||||
"update",
|
"namespaced": true,
|
||||||
"watch"
|
"kind": "ScopeDashboardBinding",
|
||||||
]
|
"verbs": [
|
||||||
},{
|
"create",
|
||||||
|
"delete",
|
||||||
|
"deletecollection",
|
||||||
|
"get",
|
||||||
|
"list",
|
||||||
|
"patch",
|
||||||
|
"update",
|
||||||
|
"watch"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
"name": "scopenodes",
|
"name": "scopenodes",
|
||||||
"singularName": "scopenode",
|
"singularName": "scopenode",
|
||||||
"namespaced": true,
|
"namespaced": true,
|
||||||
"kind": "ScopeNode",
|
"kind": "ScopeNode",
|
||||||
"verbs": [
|
"verbs": [
|
||||||
"create",
|
"create",
|
||||||
"delete",
|
"delete",
|
||||||
"deletecollection",
|
"deletecollection",
|
||||||
"get",
|
"get",
|
||||||
"list",
|
"list",
|
||||||
"patch",
|
"patch",
|
||||||
"update",
|
"update",
|
||||||
"watch"
|
"watch"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
||||||
{
|
{
|
||||||
"name": "scopes",
|
"name": "scopes",
|
||||||
"singularName": "scope",
|
"singularName": "scope",
|
||||||
@@ -94,7 +104,6 @@ func TestIntegrationScopes(t *testing.T) {
|
|||||||
"watch"
|
"watch"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
]
|
]
|
||||||
}`, string(v1Disco))
|
}`, string(v1Disco))
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user