mirror of
https://github.com/grafana/grafana.git
synced 2026-07-29 15:59:50 -05:00
K8s: Add subresource to the example apiserver (#78030)
This commit is contained in:
@@ -138,6 +138,7 @@
|
|||||||
/pkg/services/validations/ @grafana/backend-platform
|
/pkg/services/validations/ @grafana/backend-platform
|
||||||
/pkg/setting/ @grafana/backend-platform
|
/pkg/setting/ @grafana/backend-platform
|
||||||
/pkg/tests/ @grafana/backend-platform
|
/pkg/tests/ @grafana/backend-platform
|
||||||
|
/pkg/tests/apis/ @grafana/grafana-app-platform-squad
|
||||||
/pkg/tests/api/correlations/ @grafana/explore-squad
|
/pkg/tests/api/correlations/ @grafana/explore-squad
|
||||||
/pkg/tsdb/grafanads/ @grafana/backend-platform
|
/pkg/tsdb/grafanads/ @grafana/backend-platform
|
||||||
/pkg/tsdb/intervalv2/ @grafana/backend-platform
|
/pkg/tsdb/intervalv2/ @grafana/backend-platform
|
||||||
@@ -603,6 +604,7 @@ embed.go @grafana/grafana-as-code
|
|||||||
/pkg/kinds/ @grafana/grafana-as-code
|
/pkg/kinds/ @grafana/grafana-as-code
|
||||||
/pkg/cuectx/ @grafana/grafana-as-code
|
/pkg/cuectx/ @grafana/grafana-as-code
|
||||||
/pkg/registry/ @grafana/grafana-as-code
|
/pkg/registry/ @grafana/grafana-as-code
|
||||||
|
/pkg/registry/apis/ @grafana/grafana-app-platform-squad
|
||||||
/pkg/codegen/ @grafana/grafana-as-code
|
/pkg/codegen/ @grafana/grafana-as-code
|
||||||
/pkg/kinds/*/*_gen.go @grafana/grafana-as-code
|
/pkg/kinds/*/*_gen.go @grafana/grafana-as-code
|
||||||
/pkg/registry/corekind/ @grafana/grafana-as-code
|
/pkg/registry/corekind/ @grafana/grafana-as-code
|
||||||
|
|||||||
@@ -4,10 +4,12 @@ import (
|
|||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// Mirrors the info exposed in "github.com/grafana/grafana/pkg/setting"
|
||||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
type RuntimeInfo struct {
|
type RuntimeInfo struct {
|
||||||
metav1.TypeMeta `json:",inline"`
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// Unix timestamp when the process started
|
||||||
StartupTime int64 `json:"startupTime,omitempty"`
|
StartupTime int64 `json:"startupTime,omitempty"`
|
||||||
|
|
||||||
BuildVersion string `json:"buildVersion,omitempty"`
|
BuildVersion string `json:"buildVersion,omitempty"`
|
||||||
@@ -18,3 +20,28 @@ type RuntimeInfo struct {
|
|||||||
IsEnterprise bool `json:"enterprise,omitempty"`
|
IsEnterprise bool `json:"enterprise,omitempty"`
|
||||||
Packaging string `json:"packaging,omitempty"`
|
Packaging string `json:"packaging,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
type DummyResource struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
metav1.ObjectMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Spec string `json:"spec,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
type DummyResourceList struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
// +optional
|
||||||
|
metav1.ListMeta `json:"metadata,omitempty"`
|
||||||
|
|
||||||
|
Items []DummyResource `json:"items,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||||
|
type DummySubresource struct {
|
||||||
|
metav1.TypeMeta `json:",inline"`
|
||||||
|
|
||||||
|
// add subresource info here
|
||||||
|
Info string `json:"info,omitempty"`
|
||||||
|
}
|
||||||
|
|||||||
@@ -11,6 +11,90 @@ import (
|
|||||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DummyResource) DeepCopyInto(out *DummyResource) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DummyResource.
|
||||||
|
func (in *DummyResource) DeepCopy() *DummyResource {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DummyResource)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *DummyResource) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DummyResourceList) DeepCopyInto(out *DummyResourceList) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||||
|
if in.Items != nil {
|
||||||
|
in, out := &in.Items, &out.Items
|
||||||
|
*out = make([]DummyResource, len(*in))
|
||||||
|
for i := range *in {
|
||||||
|
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DummyResourceList.
|
||||||
|
func (in *DummyResourceList) DeepCopy() *DummyResourceList {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DummyResourceList)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *DummyResourceList) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
|
func (in *DummySubresource) DeepCopyInto(out *DummySubresource) {
|
||||||
|
*out = *in
|
||||||
|
out.TypeMeta = in.TypeMeta
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new DummySubresource.
|
||||||
|
func (in *DummySubresource) DeepCopy() *DummySubresource {
|
||||||
|
if in == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
out := new(DummySubresource)
|
||||||
|
in.DeepCopyInto(out)
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||||
|
func (in *DummySubresource) DeepCopyObject() runtime.Object {
|
||||||
|
if c := in.DeepCopy(); c != nil {
|
||||||
|
return c
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// 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 *RuntimeInfo) DeepCopyInto(out *RuntimeInfo) {
|
func (in *RuntimeInfo) DeepCopyInto(out *RuntimeInfo) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
// Code generated by defaulter-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
package v0alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RegisterDefaults adds defaulters functions to the given scheme.
|
||||||
|
// Public to allow building arbitrary schemes.
|
||||||
|
// All generated defaulters are covering - they call all nested defaulters.
|
||||||
|
func RegisterDefaults(scheme *runtime.Scheme) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
@@ -1,89 +0,0 @@
|
|||||||
package v0alpha1
|
|
||||||
|
|
||||||
import (
|
|
||||||
common "k8s.io/kube-openapi/pkg/common"
|
|
||||||
spec "k8s.io/kube-openapi/pkg/validation/spec"
|
|
||||||
)
|
|
||||||
|
|
||||||
// NOTE: this must match the golang fully qualified name!
|
|
||||||
const kindKey = "github.com/grafana/grafana/pkg/apis/example/v0alpha1.RuntimeInfo"
|
|
||||||
|
|
||||||
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
|
||||||
return map[string]common.OpenAPIDefinition{
|
|
||||||
kindKey: schema_pkg_apis_example_v0alpha1_RuntimeInfo(ref),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func schema_pkg_apis_example_v0alpha1_RuntimeInfo(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: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"startupTime": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int64",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"buildVersion": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"buildCommit": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"enterpriseBuildCommit": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"buildBranch": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"buildStamp": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"integer"},
|
|
||||||
Format: "int64",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"enterprise": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"boolean"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"packaging": {
|
|
||||||
SchemaProps: spec.SchemaProps{
|
|
||||||
Type: []string{"string"},
|
|
||||||
Format: "",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,220 @@
|
|||||||
|
//go:build !ignore_autogenerated
|
||||||
|
// +build !ignore_autogenerated
|
||||||
|
|
||||||
|
// SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
// Code generated by openapi-gen. DO NOT EDIT.
|
||||||
|
|
||||||
|
// This file was autogenerated by openapi-gen. Do not edit it manually!
|
||||||
|
|
||||||
|
package v0alpha1
|
||||||
|
|
||||||
|
import (
|
||||||
|
common "k8s.io/kube-openapi/pkg/common"
|
||||||
|
spec "k8s.io/kube-openapi/pkg/validation/spec"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||||
|
return map[string]common.OpenAPIDefinition{
|
||||||
|
"github.com/grafana/grafana/pkg/apis/example/v0alpha1.DummyResource": schema_pkg_apis_example_v0alpha1_DummyResource(ref),
|
||||||
|
"github.com/grafana/grafana/pkg/apis/example/v0alpha1.DummyResourceList": schema_pkg_apis_example_v0alpha1_DummyResourceList(ref),
|
||||||
|
"github.com/grafana/grafana/pkg/apis/example/v0alpha1.DummySubresource": schema_pkg_apis_example_v0alpha1_DummySubresource(ref),
|
||||||
|
"github.com/grafana/grafana/pkg/apis/example/v0alpha1.RuntimeInfo": schema_pkg_apis_example_v0alpha1_RuntimeInfo(ref),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func schema_pkg_apis_example_v0alpha1_DummyResource(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.ObjectMeta"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"spec": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func schema_pkg_apis_example_v0alpha1_DummyResourceList(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/example/v0alpha1.DummyResource"),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Dependencies: []string{
|
||||||
|
"github.com/grafana/grafana/pkg/apis/example/v0alpha1.DummyResource", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func schema_pkg_apis_example_v0alpha1_DummySubresource(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: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"info": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "add subresource info here",
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func schema_pkg_apis_example_v0alpha1_RuntimeInfo(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||||
|
return common.OpenAPIDefinition{
|
||||||
|
Schema: spec.Schema{
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Mirrors the info exposed in \"github.com/grafana/grafana/pkg/setting\"",
|
||||||
|
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: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"startupTime": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Description: "Unix timestamp when the process started",
|
||||||
|
Type: []string{"integer"},
|
||||||
|
Format: "int64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"buildVersion": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"buildCommit": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"enterpriseBuildCommit": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"buildBranch": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"buildStamp": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"integer"},
|
||||||
|
Format: "int64",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"enterprise": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"boolean"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"packaging": {
|
||||||
|
SchemaProps: spec.SchemaProps{
|
||||||
|
Type: []string{"string"},
|
||||||
|
Format: "",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
package example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
|
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
|
||||||
|
grafanarequest "github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ rest.Storage = (*dummyStorage)(nil)
|
||||||
|
_ rest.Scoper = (*dummyStorage)(nil)
|
||||||
|
_ rest.SingularNameProvider = (*dummyStorage)(nil)
|
||||||
|
_ rest.Getter = (*dummyStorage)(nil)
|
||||||
|
_ rest.Lister = (*dummyStorage)(nil)
|
||||||
|
)
|
||||||
|
|
||||||
|
type dummyStorage struct {
|
||||||
|
store *genericregistry.Store
|
||||||
|
names []string
|
||||||
|
creationTimestamp metav1.Time
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDummyStorage(gv schema.GroupVersion, scheme *runtime.Scheme, names ...string) *dummyStorage {
|
||||||
|
strategy := grafanaregistry.NewStrategy(scheme)
|
||||||
|
store := &genericregistry.Store{
|
||||||
|
NewFunc: func() runtime.Object { return &example.DummyResource{} }, // getter not supported
|
||||||
|
NewListFunc: func() runtime.Object { return &example.DummyResourceList{} }, // both list and get return the same thing
|
||||||
|
PredicateFunc: grafanaregistry.Matcher,
|
||||||
|
DefaultQualifiedResource: gv.WithResource("dummy").GroupResource(),
|
||||||
|
SingularQualifiedResource: gv.WithResource("dummy").GroupResource(),
|
||||||
|
CreateStrategy: strategy,
|
||||||
|
UpdateStrategy: strategy,
|
||||||
|
DeleteStrategy: strategy,
|
||||||
|
}
|
||||||
|
store.TableConvertor = rest.NewDefaultTableConvertor(store.DefaultQualifiedResource)
|
||||||
|
|
||||||
|
return &dummyStorage{
|
||||||
|
store: store,
|
||||||
|
names: names,
|
||||||
|
creationTimestamp: metav1.Now(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dummyStorage) New() runtime.Object {
|
||||||
|
return s.store.New()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dummyStorage) Destroy() {}
|
||||||
|
|
||||||
|
func (s *dummyStorage) NamespaceScoped() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dummyStorage) GetSingularName() string {
|
||||||
|
return "dummy"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dummyStorage) NewList() runtime.Object {
|
||||||
|
return s.store.NewListFunc()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dummyStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||||
|
return s.store.TableConvertor.ConvertToTable(ctx, object, tableOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dummyStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||||
|
info, err := grafanarequest.NamespaceInfoFrom(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
idx := slices.Index(s.names, name)
|
||||||
|
if idx < 0 {
|
||||||
|
return nil, fmt.Errorf("dummy not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
return &example.DummyResource{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: info.Value,
|
||||||
|
CreationTimestamp: s.creationTimestamp,
|
||||||
|
ResourceVersion: "1",
|
||||||
|
},
|
||||||
|
Spec: fmt.Sprintf("dummy: %s", name),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s *dummyStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||||
|
info, err := grafanarequest.NamespaceInfoFrom(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res := &example.DummyResourceList{}
|
||||||
|
for _, name := range s.names {
|
||||||
|
res.Items = append(res.Items, example.DummyResource{
|
||||||
|
ObjectMeta: metav1.ObjectMeta{
|
||||||
|
Name: name,
|
||||||
|
Namespace: info.Value,
|
||||||
|
CreationTimestamp: s.creationTimestamp,
|
||||||
|
ResourceVersion: "1",
|
||||||
|
},
|
||||||
|
Spec: fmt.Sprintf("dummy: %s", name),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
@@ -33,27 +33,50 @@ var _ grafanaapiserver.APIGroupBuilder = (*TestingAPIBuilder)(nil)
|
|||||||
// This is used just so wire has something unique to return
|
// This is used just so wire has something unique to return
|
||||||
type TestingAPIBuilder struct {
|
type TestingAPIBuilder struct {
|
||||||
codecs serializer.CodecFactory
|
codecs serializer.CodecFactory
|
||||||
|
gv schema.GroupVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder {
|
func RegisterAPIService(features featuremgmt.FeatureToggles, apiregistration grafanaapiserver.APIRegistrar) *TestingAPIBuilder {
|
||||||
if !features.IsEnabled(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
|
if !features.IsEnabled(featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs) {
|
||||||
return nil // skip registration unless opting into experimental apis
|
return nil // skip registration unless opting into experimental apis
|
||||||
}
|
}
|
||||||
builder := &TestingAPIBuilder{}
|
builder := &TestingAPIBuilder{
|
||||||
|
gv: schema.GroupVersion{Group: GroupName, Version: VersionID},
|
||||||
|
}
|
||||||
apiregistration.RegisterAPI(builder)
|
apiregistration.RegisterAPI(builder)
|
||||||
return builder
|
return builder
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *TestingAPIBuilder) GetGroupVersion() schema.GroupVersion {
|
func (b *TestingAPIBuilder) GetGroupVersion() schema.GroupVersion {
|
||||||
return SchemeGroupVersion
|
return b.gv
|
||||||
|
}
|
||||||
|
|
||||||
|
func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
|
||||||
|
scheme.AddKnownTypes(gv,
|
||||||
|
&example.RuntimeInfo{},
|
||||||
|
&example.DummyResource{},
|
||||||
|
&example.DummyResourceList{},
|
||||||
|
&example.DummySubresource{},
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *TestingAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
func (b *TestingAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
||||||
err := AddToScheme(scheme)
|
addKnownTypes(scheme, b.gv)
|
||||||
if err != nil {
|
|
||||||
return err
|
// Link this version to the internal representation.
|
||||||
}
|
// This is used for server-side-apply (PATCH), and avoids the error:
|
||||||
return scheme.SetVersionPriority(SchemeGroupVersion)
|
// "no kind is registered for the type"
|
||||||
|
addKnownTypes(scheme, schema.GroupVersion{
|
||||||
|
Group: b.gv.Group,
|
||||||
|
Version: runtime.APIVersionInternal,
|
||||||
|
})
|
||||||
|
|
||||||
|
// If multiple versions exist, then register conversions from zz_generated.conversion.go
|
||||||
|
// if err := playlist.RegisterConversions(scheme); err != nil {
|
||||||
|
// return err
|
||||||
|
// }
|
||||||
|
metav1.AddToGroupVersion(scheme, b.gv)
|
||||||
|
return scheme.SetVersionPriority(b.gv)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *TestingAPIBuilder) GetAPIGroupInfo(
|
func (b *TestingAPIBuilder) GetAPIGroupInfo(
|
||||||
@@ -61,11 +84,14 @@ func (b *TestingAPIBuilder) GetAPIGroupInfo(
|
|||||||
codecs serializer.CodecFactory, // pointer?
|
codecs serializer.CodecFactory, // pointer?
|
||||||
optsGetter generic.RESTOptionsGetter,
|
optsGetter generic.RESTOptionsGetter,
|
||||||
) (*genericapiserver.APIGroupInfo, error) {
|
) (*genericapiserver.APIGroupInfo, error) {
|
||||||
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(GroupName, scheme, metav1.ParameterCodec, codecs)
|
|
||||||
storage := map[string]rest.Storage{}
|
|
||||||
storage["runtime"] = newDeploymentInfoStorage()
|
|
||||||
apiGroupInfo.VersionedResourcesStorageMap[VersionID] = storage
|
|
||||||
b.codecs = codecs
|
b.codecs = codecs
|
||||||
|
apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(GroupName, scheme, metav1.ParameterCodec, codecs)
|
||||||
|
|
||||||
|
storage := map[string]rest.Storage{}
|
||||||
|
storage["runtime"] = newDeploymentInfoStorage(b.gv, scheme)
|
||||||
|
storage["dummy"] = newDummyStorage(b.gv, scheme, "test1", "test2", "test3")
|
||||||
|
storage["dummy/sub"] = &dummySubresourceREST{}
|
||||||
|
apiGroupInfo.VersionedResourcesStorageMap[VersionID] = storage
|
||||||
return &apiGroupInfo, nil
|
return &apiGroupInfo, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -170,29 +196,3 @@ func (b *TestingAPIBuilder) GetAPIRoutes() *grafanaapiserver.APIRoutes {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// SchemeGroupVersion is group version used to register these objects
|
|
||||||
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: VersionID}
|
|
||||||
|
|
||||||
// Resource takes an unqualified resource and returns a Group qualified GroupResource
|
|
||||||
func Resource(resource string) schema.GroupResource {
|
|
||||||
return SchemeGroupVersion.WithResource(resource).GroupResource()
|
|
||||||
}
|
|
||||||
|
|
||||||
var (
|
|
||||||
// SchemeBuilder points to a list of functions added to Scheme.
|
|
||||||
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
|
|
||||||
localSchemeBuilder = &SchemeBuilder
|
|
||||||
// AddToScheme is a common registration function for mapping packaged scoped group & version keys to a scheme.
|
|
||||||
AddToScheme = localSchemeBuilder.AddToScheme
|
|
||||||
)
|
|
||||||
|
|
||||||
// Adds the list of known types to the given scheme.
|
|
||||||
func addKnownTypes(scheme *runtime.Scheme) error {
|
|
||||||
scheme.AddKnownTypes(SchemeGroupVersion,
|
|
||||||
&example.RuntimeInfo{},
|
|
||||||
)
|
|
||||||
|
|
||||||
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
|
|
||||||
return scheme.SetVersionPriority(SchemeGroupVersion)
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -7,25 +7,43 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
"k8s.io/apimachinery/pkg/runtime"
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
genericregistry "k8s.io/apiserver/pkg/registry/generic/registry"
|
||||||
"k8s.io/apiserver/pkg/registry/rest"
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
|
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
|
||||||
|
grafanaregistry "github.com/grafana/grafana/pkg/services/grafana-apiserver/registry/generic"
|
||||||
"github.com/grafana/grafana/pkg/setting"
|
"github.com/grafana/grafana/pkg/setting"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
|
_ rest.Storage = (*staticStorage)(nil)
|
||||||
_ rest.Scoper = (*staticStorage)(nil)
|
_ rest.Scoper = (*staticStorage)(nil)
|
||||||
_ rest.SingularNameProvider = (*staticStorage)(nil)
|
_ rest.SingularNameProvider = (*staticStorage)(nil)
|
||||||
_ rest.Lister = (*staticStorage)(nil)
|
_ rest.Lister = (*staticStorage)(nil)
|
||||||
_ rest.Storage = (*staticStorage)(nil)
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type staticStorage struct {
|
type staticStorage struct {
|
||||||
info example.RuntimeInfo
|
Store *genericregistry.Store
|
||||||
|
info example.RuntimeInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func newDeploymentInfoStorage() *staticStorage {
|
func newDeploymentInfoStorage(gv schema.GroupVersion, scheme *runtime.Scheme) *staticStorage {
|
||||||
|
strategy := grafanaregistry.NewStrategy(scheme)
|
||||||
|
store := &genericregistry.Store{
|
||||||
|
NewFunc: func() runtime.Object { return &example.RuntimeInfo{} }, // getter not supported
|
||||||
|
NewListFunc: func() runtime.Object { return &example.RuntimeInfo{} }, // both list and get return the same thing
|
||||||
|
PredicateFunc: grafanaregistry.Matcher,
|
||||||
|
DefaultQualifiedResource: gv.WithResource("runtime").GroupResource(),
|
||||||
|
SingularQualifiedResource: gv.WithResource("runtime").GroupResource(),
|
||||||
|
CreateStrategy: strategy,
|
||||||
|
UpdateStrategy: strategy,
|
||||||
|
DeleteStrategy: strategy,
|
||||||
|
}
|
||||||
|
store.TableConvertor = rest.NewDefaultTableConvertor(store.DefaultQualifiedResource)
|
||||||
|
|
||||||
return &staticStorage{
|
return &staticStorage{
|
||||||
|
Store: store,
|
||||||
info: example.RuntimeInfo{
|
info: example.RuntimeInfo{
|
||||||
TypeMeta: metav1.TypeMeta{
|
TypeMeta: metav1.TypeMeta{
|
||||||
APIVersion: APIVersion,
|
APIVersion: APIVersion,
|
||||||
@@ -44,7 +62,7 @@ func newDeploymentInfoStorage() *staticStorage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticStorage) New() runtime.Object {
|
func (s *staticStorage) New() runtime.Object {
|
||||||
return &example.RuntimeInfo{}
|
return s.Store.New()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticStorage) Destroy() {}
|
func (s *staticStorage) Destroy() {}
|
||||||
@@ -58,11 +76,11 @@ func (s *staticStorage) GetSingularName() string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticStorage) NewList() runtime.Object {
|
func (s *staticStorage) NewList() runtime.Object {
|
||||||
return &example.RuntimeInfo{}
|
return s.Store.NewListFunc()
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
func (s *staticStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||||
return rest.NewDefaultTableConvertor(Resource("runtime")).ConvertToTable(ctx, object, tableOptions)
|
return s.Store.TableConvertor.ConvertToTable(ctx, object, tableOptions)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *staticStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
func (s *staticStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||||
|
|||||||
@@ -0,0 +1,54 @@
|
|||||||
|
package example
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"k8s.io/apimachinery/pkg/runtime"
|
||||||
|
"k8s.io/apiserver/pkg/registry/rest"
|
||||||
|
|
||||||
|
example "github.com/grafana/grafana/pkg/apis/example/v0alpha1"
|
||||||
|
"github.com/grafana/grafana/pkg/infra/appcontext"
|
||||||
|
"github.com/grafana/grafana/pkg/services/grafana-apiserver/endpoints/request"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dummySubresourceREST struct{}
|
||||||
|
|
||||||
|
var _ = rest.Connecter(&dummySubresourceREST{})
|
||||||
|
|
||||||
|
func (r *dummySubresourceREST) New() runtime.Object {
|
||||||
|
return &example.DummySubresource{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *dummySubresourceREST) Destroy() {
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *dummySubresourceREST) ConnectMethods() []string {
|
||||||
|
return []string{"GET"}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *dummySubresourceREST) NewConnectOptions() (runtime.Object, bool, string) {
|
||||||
|
return nil, false, "" // true means you can use the trailing path as a variable
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r *dummySubresourceREST) Connect(ctx context.Context, name string, opts runtime.Object, responder rest.Responder) (http.Handler, error) {
|
||||||
|
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
user, err := appcontext.User(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// This response object format is negotiated by k8s
|
||||||
|
dummy := &example.DummySubresource{
|
||||||
|
Info: fmt.Sprintf("%s/%s", info.Value, user.Login),
|
||||||
|
}
|
||||||
|
|
||||||
|
return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||||
|
responder.Object(http.StatusOK, dummy)
|
||||||
|
}), nil
|
||||||
|
}
|
||||||
@@ -42,16 +42,12 @@ type APIRouteHandler struct {
|
|||||||
Handler http.HandlerFunc // when Level = resource, the resource will be available in context
|
Handler http.HandlerFunc // when Level = resource, the resource will be available in context
|
||||||
}
|
}
|
||||||
|
|
||||||
// APIRoutes define the
|
// APIRoutes define explicit HTTP handlers in an apiserver
|
||||||
|
// TBD: is this actually necessary -- there may be more k8s native options for this
|
||||||
type APIRoutes struct {
|
type APIRoutes struct {
|
||||||
// Root handlers are registered directly after the apiVersion identifier
|
// Root handlers are registered directly after the apiVersion identifier
|
||||||
Root []APIRouteHandler
|
Root []APIRouteHandler
|
||||||
|
|
||||||
// Namespace handlers are mounted under the namespace
|
// Namespace handlers are mounted under the namespace
|
||||||
Namespace []APIRouteHandler
|
Namespace []APIRouteHandler
|
||||||
|
|
||||||
// Resource routes behave the same as pod/logs
|
|
||||||
// it looks like a sub-resource, however the response is backed directly by an http handler
|
|
||||||
// The current resource can be fetched through context
|
|
||||||
Resource map[string]APIRouteHandler
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,22 +71,6 @@ func getAPIHandler(delegateHandler http.Handler, restConfig *restclient.Config,
|
|||||||
sub.HandleFunc(route.Path, route.Handler).
|
sub.HandleFunc(route.Path, route.Handler).
|
||||||
Methods(methods...)
|
Methods(methods...)
|
||||||
}
|
}
|
||||||
|
|
||||||
// getter := makeGetter(restConfig)
|
|
||||||
for resource, route := range routes.Resource {
|
|
||||||
err = validPath(route.Path)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
fmt.Printf("TODO: %s/%v\n", resource, route)
|
|
||||||
|
|
||||||
// get a client for that resource kind
|
|
||||||
//getter := makeGetter(restConfig)
|
|
||||||
|
|
||||||
useful = true
|
|
||||||
// sub.HandleFunc(v.Slug, SubresourceHandlerWrapper(v.Handler, getter)).
|
|
||||||
// Methods(methods...)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if !useful {
|
if !useful {
|
||||||
@@ -184,12 +168,6 @@ func getOpenAPIPostProcessor(builders []APIGroupBuilder) func(*spec3.OpenAPI) (*
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for resource, route := range routes.Resource {
|
|
||||||
copy.Paths.Paths[prefix+"/namespaces/{namespace}/"+resource+"{name}"+route.Path] = &spec3.Path{
|
|
||||||
PathProps: *route.Spec,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return ©, nil
|
return ©, nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,167 @@
|
|||||||
|
package playlist
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||||
|
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||||
|
|
||||||
|
"github.com/grafana/grafana/pkg/services/featuremgmt"
|
||||||
|
"github.com/grafana/grafana/pkg/tests/apis"
|
||||||
|
"github.com/grafana/grafana/pkg/tests/testinfra"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestExampleApp(t *testing.T) {
|
||||||
|
if testing.Short() {
|
||||||
|
t.Skip("skipping integration test")
|
||||||
|
}
|
||||||
|
helper := apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||||
|
AppModeProduction: true, // do not start extra port 6443
|
||||||
|
DisableAnonymous: true,
|
||||||
|
EnableFeatureToggles: []string{
|
||||||
|
featuremgmt.FlagGrafanaAPIServer,
|
||||||
|
featuremgmt.FlagGrafanaAPIServerWithExperimentalAPIs, // Required to start the example service
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check runtime info resource", func(t *testing.T) {
|
||||||
|
// Resource is not namespaced!
|
||||||
|
client := helper.Org1.Admin.Client.Resource(schema.GroupVersionResource{
|
||||||
|
Group: "example.grafana.app",
|
||||||
|
Version: "v0alpha1",
|
||||||
|
Resource: "runtime",
|
||||||
|
})
|
||||||
|
rsp, err := client.List(context.Background(), metav1.ListOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
v, ok := rsp.Object["startupTime"].(int64)
|
||||||
|
require.True(t, ok)
|
||||||
|
require.Greater(t, v, time.Now().Add(-1*time.Hour).UnixMilli()) // should be within the last hour
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check discovery client", func(t *testing.T) {
|
||||||
|
disco := helper.NewDiscoveryClient()
|
||||||
|
resources, err := disco.ServerResourcesForGroupVersion("example.grafana.app/v0alpha1")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
v1Disco, err := json.MarshalIndent(resources, "", " ")
|
||||||
|
require.NoError(t, err)
|
||||||
|
// fmt.Printf("%s", string(v1Disco))
|
||||||
|
|
||||||
|
require.JSONEq(t, `{
|
||||||
|
"kind": "APIResourceList",
|
||||||
|
"apiVersion": "v1",
|
||||||
|
"groupVersion": "example.grafana.app/v0alpha1",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"name": "dummy",
|
||||||
|
"singularName": "dummy",
|
||||||
|
"namespaced": true,
|
||||||
|
"kind": "DummyResource",
|
||||||
|
"verbs": [
|
||||||
|
"get",
|
||||||
|
"list"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "dummy/sub",
|
||||||
|
"singularName": "",
|
||||||
|
"namespaced": true,
|
||||||
|
"kind": "DummySubresource",
|
||||||
|
"verbs": [
|
||||||
|
"get"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "runtime",
|
||||||
|
"singularName": "runtime",
|
||||||
|
"namespaced": false,
|
||||||
|
"kind": "RuntimeInfo",
|
||||||
|
"verbs": [
|
||||||
|
"list"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}`, string(v1Disco))
|
||||||
|
|
||||||
|
//fmt.Printf("%s", string(v1Disco))
|
||||||
|
require.JSONEq(t, `[
|
||||||
|
{
|
||||||
|
"version": "v0alpha1",
|
||||||
|
"freshness": "Current",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"resource": "dummy",
|
||||||
|
"responseKind": {
|
||||||
|
"group": "",
|
||||||
|
"kind": "DummyResource",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
"scope": "Namespaced",
|
||||||
|
"singularResource": "dummy",
|
||||||
|
"subresources": [
|
||||||
|
{
|
||||||
|
"responseKind": {
|
||||||
|
"group": "",
|
||||||
|
"kind": "DummySubresource",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
"subresource": "sub",
|
||||||
|
"verbs": [
|
||||||
|
"get"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"verbs": [
|
||||||
|
"get",
|
||||||
|
"list"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"resource": "runtime",
|
||||||
|
"responseKind": {
|
||||||
|
"group": "",
|
||||||
|
"kind": "RuntimeInfo",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
"scope": "Cluster",
|
||||||
|
"singularResource": "runtime",
|
||||||
|
"verbs": [
|
||||||
|
"list"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]`, helper.GetGroupVersionInfoJSON("example.grafana.app"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Check dummy with subresource", func(t *testing.T) {
|
||||||
|
client := helper.Org1.Viewer.Client.Resource(schema.GroupVersionResource{
|
||||||
|
Group: "example.grafana.app",
|
||||||
|
Version: "v0alpha1",
|
||||||
|
Resource: "dummy",
|
||||||
|
}).Namespace("default")
|
||||||
|
rsp, err := client.Get(context.Background(), "test2", metav1.GetOptions{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Equal(t, "dummy: test2", rsp.Object["spec"])
|
||||||
|
require.Equal(t, "DummyResource", rsp.GetObjectKind().GroupVersionKind().Kind)
|
||||||
|
|
||||||
|
// Now a sub-resource
|
||||||
|
rsp, err = client.Get(context.Background(), "test2", metav1.GetOptions{}, "sub")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
raw, err := json.MarshalIndent(rsp, "", " ")
|
||||||
|
require.NoError(t, err)
|
||||||
|
//fmt.Printf("%s", string(raw))
|
||||||
|
require.JSONEq(t, `{
|
||||||
|
"apiVersion": "example.grafana.app/v0alpha1",
|
||||||
|
"kind": "DummySubresource",
|
||||||
|
"info": "default/viewer-1"
|
||||||
|
}`, string(raw))
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
|
"k8s.io/apimachinery/pkg/runtime/serializer/yaml"
|
||||||
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
|
yamlutil "k8s.io/apimachinery/pkg/util/yaml"
|
||||||
|
|
||||||
|
"k8s.io/client-go/discovery"
|
||||||
"k8s.io/client-go/dynamic"
|
"k8s.io/client-go/dynamic"
|
||||||
"k8s.io/client-go/rest"
|
"k8s.io/client-go/rest"
|
||||||
|
|
||||||
@@ -397,7 +398,60 @@ func (c K8sTestHelper) createTestUsers(orgName string) OrgUsers {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c K8sTestHelper) CreateDS(cmd *datasources.AddDataSourceCommand) *datasources.DataSource {
|
func (c *K8sTestHelper) NewDiscoveryClient() *discovery.DiscoveryClient {
|
||||||
|
c.t.Helper()
|
||||||
|
|
||||||
|
baseUrl := fmt.Sprintf("http://%s", c.env.Server.HTTPServer.Listener.Addr())
|
||||||
|
conf := &rest.Config{
|
||||||
|
Host: baseUrl,
|
||||||
|
Username: c.Org1.Admin.Identity.GetLogin(),
|
||||||
|
Password: c.Org1.Admin.password,
|
||||||
|
}
|
||||||
|
client, err := discovery.NewDiscoveryClientForConfig(conf)
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
return client
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *K8sTestHelper) GetGroupVersionInfoJSON(group string) string {
|
||||||
|
c.t.Helper()
|
||||||
|
|
||||||
|
disco := c.NewDiscoveryClient()
|
||||||
|
req := disco.RESTClient().Get().
|
||||||
|
Prefix("apis").
|
||||||
|
SetHeader("Accept", "application/json;g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList,application/json")
|
||||||
|
|
||||||
|
result := req.Do(context.Background())
|
||||||
|
require.NoError(c.t, result.Error())
|
||||||
|
|
||||||
|
type DiscoItem struct {
|
||||||
|
Metadata struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
} `json:"metadata"`
|
||||||
|
Versions []any `json:"versions,omitempty"`
|
||||||
|
}
|
||||||
|
type DiscoList struct {
|
||||||
|
Items []DiscoItem `json:"items"`
|
||||||
|
}
|
||||||
|
|
||||||
|
raw, err := result.Raw()
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
all := &DiscoList{}
|
||||||
|
err = json.Unmarshal(raw, all)
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
|
||||||
|
for _, item := range all.Items {
|
||||||
|
if item.Metadata.Name == group {
|
||||||
|
v, err := json.MarshalIndent(item.Versions, "", " ")
|
||||||
|
require.NoError(c.t, err)
|
||||||
|
return string(v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Fail(c.t, "could not find discovery info for: ", group)
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *K8sTestHelper) CreateDS(cmd *datasources.AddDataSourceCommand) *datasources.DataSource {
|
||||||
c.t.Helper()
|
c.t.Helper()
|
||||||
|
|
||||||
dataSource, err := c.env.Server.HTTPServer.DataSourcesService.AddDataSource(context.Background(), cmd)
|
dataSource, err := c.env.Server.HTTPServer.DataSourcesService.AddDataSource(context.Background(), cmd)
|
||||||
|
|||||||
@@ -26,13 +26,43 @@ func TestPlaylist(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
t.Run("default setup", func(t *testing.T) {
|
t.Run("default setup", func(t *testing.T) {
|
||||||
doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
h := doPlaylistTests(t, apis.NewK8sTestHelper(t, testinfra.GrafanaOpts{
|
||||||
AppModeProduction: true, // do not start extra port 6443
|
AppModeProduction: true, // do not start extra port 6443
|
||||||
DisableAnonymous: true,
|
DisableAnonymous: true,
|
||||||
EnableFeatureToggles: []string{
|
EnableFeatureToggles: []string{
|
||||||
featuremgmt.FlagGrafanaAPIServer,
|
featuremgmt.FlagGrafanaAPIServer,
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
// The accepted verbs will change when dual write is enabled
|
||||||
|
disco := h.GetGroupVersionInfoJSON("playlist.grafana.app")
|
||||||
|
// fmt.Printf("%s", string(disco))
|
||||||
|
require.JSONEq(t, `[
|
||||||
|
{
|
||||||
|
"version": "v0alpha1",
|
||||||
|
"freshness": "Current",
|
||||||
|
"resources": [
|
||||||
|
{
|
||||||
|
"resource": "playlists",
|
||||||
|
"responseKind": {
|
||||||
|
"group": "",
|
||||||
|
"kind": "Playlist",
|
||||||
|
"version": ""
|
||||||
|
},
|
||||||
|
"scope": "Namespaced",
|
||||||
|
"singularResource": "playlist",
|
||||||
|
"verbs": [
|
||||||
|
"create",
|
||||||
|
"delete",
|
||||||
|
"get",
|
||||||
|
"list",
|
||||||
|
"patch",
|
||||||
|
"update"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]`, disco)
|
||||||
})
|
})
|
||||||
|
|
||||||
t.Run("with k8s api flag", func(t *testing.T) {
|
t.Run("with k8s api flag", func(t *testing.T) {
|
||||||
@@ -47,17 +77,13 @@ func TestPlaylist(t *testing.T) {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func doPlaylistTests(t *testing.T, helper *apis.K8sTestHelper) {
|
func doPlaylistTests(t *testing.T, helper *apis.K8sTestHelper) *apis.K8sTestHelper {
|
||||||
gvr := schema.GroupVersionResource{
|
gvr := schema.GroupVersionResource{
|
||||||
Group: "playlist.grafana.app",
|
Group: "playlist.grafana.app",
|
||||||
Version: "v0alpha1",
|
Version: "v0alpha1",
|
||||||
Resource: "playlists",
|
Resource: "playlists",
|
||||||
}
|
}
|
||||||
|
|
||||||
defer func() {
|
|
||||||
helper.Shutdown()
|
|
||||||
}()
|
|
||||||
|
|
||||||
t.Run("Check direct List permissions from different org users", func(t *testing.T) {
|
t.Run("Check direct List permissions from different org users", func(t *testing.T) {
|
||||||
// Check view permissions
|
// Check view permissions
|
||||||
rsp := helper.List(helper.Org1.Viewer, "default", gvr)
|
rsp := helper.List(helper.Org1.Viewer, "default", gvr)
|
||||||
@@ -332,6 +358,8 @@ func doPlaylistTests(t *testing.T, helper *apis.K8sTestHelper) {
|
|||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Empty(t, list.Items)
|
require.Empty(t, list.Items)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
return helper
|
||||||
}
|
}
|
||||||
|
|
||||||
// typescript style map function
|
// typescript style map function
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import config from 'app/core/config';
|
import config from 'app/core/config';
|
||||||
|
|
||||||
export function getThemeColor(dark: string, light: string): string {
|
export function getThemeColor(dark: string, light: string): string {
|
||||||
return config.bootData.user.lightTheme ? light : dark;
|
return config.bootData?.user?.lightTheme ? light : dark;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user