with configs
This commit is contained in:
@@ -0,0 +1,57 @@
|
||||
package v0alpha1
|
||||
|
||||
import (
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
common "github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1"
|
||||
)
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type GenericDataSource struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ObjectMeta `json:"metadata"`
|
||||
|
||||
// generic config
|
||||
Spec GenericDataSourceSpec `json:"spec"`
|
||||
|
||||
// Secure values placeholder (true for fields that exist)
|
||||
Secure map[string]bool `json:"secure,omitempty"`
|
||||
|
||||
// // swagger:ignore
|
||||
// Password string `json:"-"`
|
||||
|
||||
// // swagger:ignore
|
||||
// BasicAuthPassword string `json:"-"`
|
||||
}
|
||||
|
||||
type DsAccess string // proxy | direct
|
||||
|
||||
type GenericDataSourceSpec struct {
|
||||
// The diplay name (NOTE, this used to be the "name")
|
||||
Title string `json:"title"`
|
||||
|
||||
// Direct or proxy?
|
||||
Access DsAccess `json:"access,omitempty"`
|
||||
|
||||
// Server URL
|
||||
URL string `json:"url,omitempty"`
|
||||
|
||||
User string `json:"use,omitempty"`
|
||||
Database string `json:"database,omitempty"`
|
||||
BasicAuth bool `json:"basicAuth,omitempty"`
|
||||
BasicAuthUser string `json:"basicAuthUser,omitempty"`
|
||||
WithCredentials bool `json:"withCredentials,omitempty"`
|
||||
IsDefault bool `json:"isDefault,omitempty"`
|
||||
ReadOnly bool `json:"readOnly,omitempty"`
|
||||
|
||||
// Generic unstructured configuration settings
|
||||
JsonData common.Unstructured `json:"jsonData,omitempty"`
|
||||
}
|
||||
|
||||
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
|
||||
type GenericDataSourceList struct {
|
||||
metav1.TypeMeta `json:",inline"`
|
||||
metav1.ListMeta `json:"metadata"`
|
||||
|
||||
Items []GenericDataSource `json:"items"`
|
||||
}
|
||||
@@ -4,9 +4,10 @@ import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -14,6 +15,32 @@ const (
|
||||
VERSION = "v0alpha1"
|
||||
)
|
||||
|
||||
var GenericDataSourceResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
|
||||
"datasoruce", "datasoruce", "DataSource",
|
||||
func() runtime.Object { return &GenericDataSource{} },
|
||||
func() runtime.Object { return &GenericDataSourceList{} },
|
||||
utils.TableColumns{
|
||||
Definition: []metav1.TableColumnDefinition{
|
||||
{Name: "Name", Type: "string", Format: "name"},
|
||||
{Name: "Title", Type: "string", Format: "string", Description: "The datasource title"},
|
||||
{Name: "APIVersion", Type: "string", Format: "string", Description: "API Version"},
|
||||
{Name: "Created At", Type: "date"},
|
||||
},
|
||||
Reader: func(obj any) ([]interface{}, error) {
|
||||
m, ok := obj.(*GenericDataSource)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected connection")
|
||||
}
|
||||
return []interface{}{
|
||||
m.Name,
|
||||
"???",
|
||||
m.APIVersion,
|
||||
m.CreationTimestamp.UTC().Format(time.RFC3339),
|
||||
}, nil
|
||||
},
|
||||
},
|
||||
)
|
||||
|
||||
var GenericConnectionResourceInfo = utils.NewResourceInfo(GROUP, VERSION,
|
||||
"connections", "connection", "DataSourceConnection",
|
||||
func() runtime.Object { return &DataSourceConnection{} },
|
||||
|
||||
@@ -70,6 +70,90 @@ func (in *DataSourceConnectionList) DeepCopyObject() runtime.Object {
|
||||
return nil
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *GenericDataSource) DeepCopyInto(out *GenericDataSource) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
|
||||
in.Spec.DeepCopyInto(&out.Spec)
|
||||
if in.Secure != nil {
|
||||
in, out := &in.Secure, &out.Secure
|
||||
*out = make(map[string]bool, len(*in))
|
||||
for key, val := range *in {
|
||||
(*out)[key] = val
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericDataSource.
|
||||
func (in *GenericDataSource) DeepCopy() *GenericDataSource {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericDataSource)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *GenericDataSource) 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 *GenericDataSourceList) DeepCopyInto(out *GenericDataSourceList) {
|
||||
*out = *in
|
||||
out.TypeMeta = in.TypeMeta
|
||||
in.ListMeta.DeepCopyInto(&out.ListMeta)
|
||||
if in.Items != nil {
|
||||
in, out := &in.Items, &out.Items
|
||||
*out = make([]GenericDataSource, len(*in))
|
||||
for i := range *in {
|
||||
(*in)[i].DeepCopyInto(&(*out)[i])
|
||||
}
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericDataSourceList.
|
||||
func (in *GenericDataSourceList) DeepCopy() *GenericDataSourceList {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericDataSourceList)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
|
||||
func (in *GenericDataSourceList) 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 *GenericDataSourceSpec) DeepCopyInto(out *GenericDataSourceSpec) {
|
||||
*out = *in
|
||||
in.JsonData.DeepCopyInto(&out.JsonData)
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GenericDataSourceSpec.
|
||||
func (in *GenericDataSourceSpec) DeepCopy() *GenericDataSourceSpec {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(GenericDataSourceSpec)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HealthCheckResult) DeepCopyInto(out *HealthCheckResult) {
|
||||
*out = *in
|
||||
|
||||
@@ -16,6 +16,9 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
return map[string]common.OpenAPIDefinition{
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.DataSourceConnection": schema_pkg_apis_datasource_v0alpha1_DataSourceConnection(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.DataSourceConnectionList": schema_pkg_apis_datasource_v0alpha1_DataSourceConnectionList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSource": schema_pkg_apis_datasource_v0alpha1_GenericDataSource(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceList": schema_pkg_apis_datasource_v0alpha1_GenericDataSourceList(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec": schema_pkg_apis_datasource_v0alpha1_GenericDataSourceSpec(ref),
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.HealthCheckResult": schema_pkg_apis_datasource_v0alpha1_HealthCheckResult(ref),
|
||||
}
|
||||
}
|
||||
@@ -118,6 +121,197 @@ func schema_pkg_apis_datasource_v0alpha1_DataSourceConnectionList(ref common.Ref
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_datasource_v0alpha1_GenericDataSource(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{
|
||||
Description: "generic config",
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec"),
|
||||
},
|
||||
},
|
||||
"secure": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Secure values placeholder (true for fields that exist)",
|
||||
Type: []string{"object"},
|
||||
AdditionalProperties: &spec.SchemaOrBool{
|
||||
Allows: true,
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: false,
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "spec"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSourceSpec", "k8s.io/apimachinery/pkg/apis/meta/v1.ObjectMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_datasource_v0alpha1_GenericDataSourceList(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/datasource/v0alpha1.GenericDataSource"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"metadata", "items"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1.GenericDataSource", "k8s.io/apimachinery/pkg/apis/meta/v1.ListMeta"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_datasource_v0alpha1_GenericDataSourceSpec(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"title": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "The diplay name (NOTE, this used to be the \"name\")",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"access": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Direct or proxy?",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"url": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Server URL",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"use": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"database": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"basicAuth": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"basicAuthUser": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"withCredentials": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"isDefault": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"readOnly": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Type: []string{"boolean"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"jsonData": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Generic unstructured configuration settings",
|
||||
Ref: ref("github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"),
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"title"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"github.com/grafana/grafana/pkg/apimachinery/apis/common/v0alpha1.Unstructured"},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_pkg_apis_datasource_v0alpha1_HealthCheckResult(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
API rule violation: names_match,github.com/grafana/grafana/pkg/apis/datasource/v0alpha1,GenericDataSourceSpec,User
|
||||
API rule violation: streaming_list_type_json_tags,github.com/grafana/grafana/pkg/apis/datasource/v0alpha1,GenericDataSourceList,ListMeta
|
||||
@@ -0,0 +1,79 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
)
|
||||
|
||||
type converter struct {
|
||||
mapper request.NamespaceMapper
|
||||
}
|
||||
|
||||
func asConnection(ds *datasources.DataSource, ns string) (*v0alpha1.DataSourceConnection, error) {
|
||||
v := &v0alpha1.DataSourceConnection{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ds.UID,
|
||||
Namespace: ns,
|
||||
CreationTimestamp: metav1.NewTime(ds.Created),
|
||||
ResourceVersion: fmt.Sprintf("%d", ds.Updated.UnixMilli()),
|
||||
},
|
||||
Title: ds.Name,
|
||||
}
|
||||
v.UID = gapiutil.CalculateClusterWideUID(v) // indicates if the value changed on the server
|
||||
meta, err := utils.MetaAccessor(v)
|
||||
if err != nil {
|
||||
meta.SetUpdatedTimestamp(&ds.Updated)
|
||||
}
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (r *converter) asConnection(ds *datasources.DataSource) (*v0alpha1.DataSourceConnection, error) {
|
||||
return asConnection(ds, r.mapper(ds.OrgID))
|
||||
}
|
||||
|
||||
func (r *converter) asGenericDataSource(ds *datasources.DataSource) (*v0alpha1.GenericDataSource, error) {
|
||||
cfg := &v0alpha1.GenericDataSource{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ds.UID,
|
||||
Namespace: r.mapper(ds.OrgID),
|
||||
CreationTimestamp: metav1.NewTime(ds.Created),
|
||||
ResourceVersion: fmt.Sprintf("%d", ds.Updated.UnixMilli()),
|
||||
},
|
||||
Spec: v0alpha1.GenericDataSourceSpec{
|
||||
Title: ds.Name,
|
||||
Access: v0alpha1.DsAccess(ds.Access),
|
||||
URL: ds.URL,
|
||||
User: ds.User,
|
||||
Database: ds.Database,
|
||||
BasicAuth: ds.BasicAuth,
|
||||
BasicAuthUser: ds.BasicAuthUser,
|
||||
WithCredentials: ds.WithCredentials,
|
||||
IsDefault: ds.IsDefault,
|
||||
ReadOnly: ds.ReadOnly,
|
||||
},
|
||||
}
|
||||
|
||||
if ds.JsonData != nil {
|
||||
val, ok := ds.JsonData.Interface().(map[string]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("expected map[string]any jsondata")
|
||||
}
|
||||
cfg.Spec.JsonData.Object = val
|
||||
}
|
||||
|
||||
if ds.SecureJsonData != nil {
|
||||
cfg.Secure = make(map[string]bool)
|
||||
for k := range ds.SecureJsonData {
|
||||
cfg.Secure[k] = true
|
||||
}
|
||||
}
|
||||
|
||||
return cfg, nil
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package datasource
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/apis/meta/internalversion"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
)
|
||||
|
||||
var (
|
||||
_ rest.Scoper = (*legacyStorage)(nil)
|
||||
_ rest.SingularNameProvider = (*legacyStorage)(nil)
|
||||
_ rest.Getter = (*legacyStorage)(nil)
|
||||
_ rest.Lister = (*legacyStorage)(nil)
|
||||
_ rest.Storage = (*legacyStorage)(nil)
|
||||
// _ rest.Creater = (*legacyStorage)(nil)
|
||||
// _ rest.Updater = (*legacyStorage)(nil)
|
||||
// _ rest.GracefulDeleter = (*legacyStorage)(nil)
|
||||
)
|
||||
|
||||
type legacyStorage struct {
|
||||
datasources PluginDatasourceProvider
|
||||
resourceInfo *utils.ResourceInfo
|
||||
}
|
||||
|
||||
func (s *legacyStorage) New() runtime.Object {
|
||||
return s.resourceInfo.NewFunc()
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Destroy() {}
|
||||
|
||||
func (s *legacyStorage) NamespaceScoped() bool {
|
||||
return true // namespace == org
|
||||
}
|
||||
|
||||
func (s *legacyStorage) GetSingularName() string {
|
||||
return s.resourceInfo.GetSingularName()
|
||||
}
|
||||
|
||||
func (s *legacyStorage) NewList() runtime.Object {
|
||||
return s.resourceInfo.NewListFunc()
|
||||
}
|
||||
|
||||
func (s *legacyStorage) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) {
|
||||
return s.resourceInfo.TableConverter().ConvertToTable(ctx, object, tableOptions)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) {
|
||||
return s.datasources.ListDataSource(ctx)
|
||||
}
|
||||
|
||||
func (s *legacyStorage) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) {
|
||||
return s.datasources.GetDataSource(ctx, name)
|
||||
}
|
||||
@@ -4,15 +4,12 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/grafana/authlib/types"
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/pluginsintegration/plugincontext"
|
||||
)
|
||||
@@ -27,6 +24,12 @@ type PluginDatasourceProvider interface {
|
||||
// List lists all data sources the user in context can see
|
||||
List(ctx context.Context) (*v0alpha1.DataSourceConnectionList, error)
|
||||
|
||||
// Get a single datasurce
|
||||
GetDataSource(ctx context.Context, uid string) (*v0alpha1.GenericDataSource, error)
|
||||
|
||||
// List all datasources
|
||||
ListDataSource(ctx context.Context) (*v0alpha1.GenericDataSourceList, error)
|
||||
|
||||
// Return settings (decrypted!) for a specific plugin
|
||||
// This will require "query" permission for the user in context
|
||||
GetInstanceSettings(ctx context.Context, uid string) (*backend.DataSourceInstanceSettings, error)
|
||||
@@ -49,6 +52,9 @@ func ProvideDefaultPluginConfigs(
|
||||
dsService: dsService,
|
||||
dsCache: dsCache,
|
||||
contextProvider: contextProvider,
|
||||
converter: &converter{
|
||||
mapper: types.OrgNamespaceFormatter, // TODO -- from cfg!!!
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -56,6 +62,7 @@ type cachingDatasourceProvider struct {
|
||||
dsService datasources.DataSourceService
|
||||
dsCache datasources.CacheService
|
||||
contextProvider *plugincontext.Provider
|
||||
converter *converter
|
||||
}
|
||||
|
||||
func (q *cachingDatasourceProvider) GetDatasourceProvider(pluginJson plugins.JSONData) PluginDatasourceProvider {
|
||||
@@ -64,6 +71,7 @@ func (q *cachingDatasourceProvider) GetDatasourceProvider(pluginJson plugins.JSO
|
||||
dsService: q.dsService,
|
||||
dsCache: q.dsCache,
|
||||
contextProvider: q.contextProvider,
|
||||
converter: q.converter,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -72,6 +80,7 @@ type scopedDatasourceProvider struct {
|
||||
dsService datasources.DataSourceService
|
||||
dsCache datasources.CacheService
|
||||
contextProvider *plugincontext.Provider
|
||||
converter *converter
|
||||
}
|
||||
|
||||
var (
|
||||
@@ -80,10 +89,6 @@ var (
|
||||
)
|
||||
|
||||
func (q *scopedDatasourceProvider) Get(ctx context.Context, uid string) (*v0alpha1.DataSourceConnection, error) {
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@@ -92,7 +97,7 @@ func (q *scopedDatasourceProvider) Get(ctx context.Context, uid string) (*v0alph
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return asConnection(ds, info.Value)
|
||||
return q.converter.asConnection(ds)
|
||||
}
|
||||
|
||||
func (q *scopedDatasourceProvider) List(ctx context.Context) (*v0alpha1.DataSourceConnectionList, error) {
|
||||
@@ -113,7 +118,7 @@ func (q *scopedDatasourceProvider) List(ctx context.Context) (*v0alpha1.DataSour
|
||||
Items: []v0alpha1.DataSourceConnection{},
|
||||
}
|
||||
for _, ds := range dss {
|
||||
v, _ := asConnection(ds, info.Value)
|
||||
v, _ := q.converter.asConnection(ds)
|
||||
result.Items = append(result.Items, *v)
|
||||
}
|
||||
return result, nil
|
||||
@@ -126,20 +131,40 @@ func (q *scopedDatasourceProvider) GetInstanceSettings(ctx context.Context, uid
|
||||
return q.contextProvider.GetDataSourceInstanceSettings(ctx, uid)
|
||||
}
|
||||
|
||||
func asConnection(ds *datasources.DataSource, ns string) (*v0alpha1.DataSourceConnection, error) {
|
||||
v := &v0alpha1.DataSourceConnection{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: ds.UID,
|
||||
Namespace: ns,
|
||||
CreationTimestamp: metav1.NewTime(ds.Created),
|
||||
ResourceVersion: fmt.Sprintf("%d", ds.Updated.UnixMilli()),
|
||||
},
|
||||
Title: ds.Name,
|
||||
}
|
||||
v.UID = gapiutil.CalculateClusterWideUID(v) // indicates if the value changed on the server
|
||||
meta, err := utils.MetaAccessor(v)
|
||||
// GetDataSource implements PluginDatasourceProvider.
|
||||
func (q *scopedDatasourceProvider) GetDataSource(ctx context.Context, uid string) (*v0alpha1.GenericDataSource, error) {
|
||||
user, err := identity.GetRequester(ctx)
|
||||
if err != nil {
|
||||
meta.SetUpdatedTimestamp(&ds.Updated)
|
||||
return nil, err
|
||||
}
|
||||
return v, err
|
||||
ds, err := q.dsCache.GetDatasourceByUID(ctx, uid, user, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return q.converter.asGenericDataSource(ds)
|
||||
}
|
||||
|
||||
// ListDataSource implements PluginDatasourceProvider.
|
||||
func (q *scopedDatasourceProvider) ListDataSource(ctx context.Context) (*v0alpha1.GenericDataSourceList, error) {
|
||||
info, err := request.NamespaceInfoFrom(ctx, true)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
dss, err := q.dsService.GetDataSourcesByType(ctx, &datasources.GetDataSourcesByTypeQuery{
|
||||
OrgID: info.OrgID,
|
||||
Type: q.plugin.ID,
|
||||
AliasIDs: q.plugin.AliasIDs,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result := &v0alpha1.GenericDataSourceList{
|
||||
Items: []v0alpha1.GenericDataSource{},
|
||||
}
|
||||
for _, ds := range dss {
|
||||
v, _ := q.converter.asGenericDataSource(ds)
|
||||
result.Items = append(result.Items, *v)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
@@ -3,6 +3,8 @@ package datasource
|
||||
import (
|
||||
"context"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/identity"
|
||||
"github.com/grafana/grafana/pkg/apimachinery/utils"
|
||||
@@ -10,7 +12,6 @@ import (
|
||||
"github.com/grafana/grafana/pkg/plugins"
|
||||
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
)
|
||||
|
||||
type QuerierFactoryFunc func(ctx context.Context, ri utils.ResourceInfo, pj plugins.JSONData) (Querier, error)
|
||||
|
||||
@@ -36,6 +36,7 @@ var _ builder.APIGroupBuilder = (*DataSourceAPIBuilder)(nil)
|
||||
// DataSourceAPIBuilder is used just so wire has something unique to return
|
||||
type DataSourceAPIBuilder struct {
|
||||
connectionResourceInfo utils.ResourceInfo
|
||||
datasourceResourceInfo utils.ResourceInfo
|
||||
|
||||
pluginJSON plugins.JSONData
|
||||
client PluginClient // will only ever be called with the same pluginid!
|
||||
@@ -114,13 +115,14 @@ func NewDataSourceAPIBuilder(
|
||||
accessControl accesscontrol.AccessControl,
|
||||
loadQueryTypes bool,
|
||||
) (*DataSourceAPIBuilder, error) {
|
||||
ri, err := resourceFromPluginID(plugin.ID)
|
||||
group, err := plugins.GetDatasourceGroupNameFromPluginID(plugin.ID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
builder := &DataSourceAPIBuilder{
|
||||
connectionResourceInfo: ri,
|
||||
connectionResourceInfo: datasource.GenericConnectionResourceInfo.WithGroupAndShortName(group, plugin.ID+"-connection"),
|
||||
datasourceResourceInfo: datasource.GenericDataSourceResourceInfo.WithGroupAndShortName(group, plugin.ID+"-config"),
|
||||
pluginJSON: plugin,
|
||||
client: client,
|
||||
datasources: datasources,
|
||||
@@ -130,7 +132,7 @@ func NewDataSourceAPIBuilder(
|
||||
}
|
||||
if loadQueryTypes {
|
||||
// In the future, this will somehow come from the plugin
|
||||
builder.queryTypes, err = getHardcodedQueryTypes(ri.GroupResource().Group)
|
||||
builder.queryTypes, err = getHardcodedQueryTypes(group)
|
||||
}
|
||||
return builder, err
|
||||
}
|
||||
@@ -164,8 +166,11 @@ func addKnownTypes(scheme *runtime.Scheme, gv schema.GroupVersion) {
|
||||
scheme.AddKnownTypes(gv,
|
||||
&datasource.DataSourceConnection{},
|
||||
&datasource.DataSourceConnectionList{},
|
||||
&datasource.GenericDataSource{},
|
||||
&datasource.GenericDataSourceList{},
|
||||
&datasource.HealthCheckResult{},
|
||||
&unstructured.Unstructured{},
|
||||
|
||||
// Query handler
|
||||
&query.QueryDataRequest{},
|
||||
&query.QueryDataResponse{},
|
||||
@@ -195,17 +200,27 @@ func (b *DataSourceAPIBuilder) InstallSchema(scheme *runtime.Scheme) error {
|
||||
return scheme.SetVersionPriority(gv)
|
||||
}
|
||||
|
||||
func resourceFromPluginID(pluginID string) (utils.ResourceInfo, error) {
|
||||
group, err := plugins.GetDatasourceGroupNameFromPluginID(pluginID)
|
||||
if err != nil {
|
||||
return utils.ResourceInfo{}, err
|
||||
}
|
||||
return datasource.GenericConnectionResourceInfo.WithGroupAndShortName(group, pluginID+"-connection"), nil
|
||||
}
|
||||
|
||||
func (b *DataSourceAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, _ builder.APIGroupOptions) error {
|
||||
func (b *DataSourceAPIBuilder) UpdateAPIGroupInfo(apiGroupInfo *genericapiserver.APIGroupInfo, opts builder.APIGroupOptions) error {
|
||||
storage := map[string]rest.Storage{}
|
||||
|
||||
ds := b.datasourceResourceInfo
|
||||
legacyStore := &legacyStorage{
|
||||
datasources: b.datasources,
|
||||
resourceInfo: &ds,
|
||||
}
|
||||
storage[ds.StoragePath()] = legacyStore
|
||||
|
||||
// if false {
|
||||
// unified, err := grafanaregistry.NewRegistryStore(opts.Scheme, ds, opts.OptsGetter)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// storage[ds.StoragePath()], err = opts.DualWriteBuilder(ds.GroupResource(), legacyStore, unified)
|
||||
// if err != nil {
|
||||
// return err
|
||||
// }
|
||||
// }
|
||||
|
||||
conn := b.connectionResourceInfo
|
||||
storage[conn.StoragePath()] = &connectionAccess{
|
||||
datasources: b.datasources,
|
||||
|
||||
@@ -8,14 +8,15 @@ import (
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
|
||||
"github.com/grafana/grafana-plugin-sdk-go/backend"
|
||||
"github.com/grafana/grafana/pkg/apis/datasource/v0alpha1"
|
||||
"github.com/grafana/grafana/pkg/infra/log"
|
||||
"github.com/grafana/grafana/pkg/services/datasources"
|
||||
"github.com/grafana/grafana/pkg/services/ngalert/models"
|
||||
"github.com/stretchr/testify/require"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
func TestSubQueryConnect(t *testing.T) {
|
||||
@@ -115,9 +116,21 @@ func (m mockResponder) Object(statusCode int, obj runtime.Object) {
|
||||
func (m mockResponder) Error(err error) {
|
||||
}
|
||||
|
||||
var _ PluginDatasourceProvider = (*mockDatasources)(nil)
|
||||
|
||||
type mockDatasources struct {
|
||||
}
|
||||
|
||||
// GetDataSource implements PluginDatasourceProvider.
|
||||
func (m mockDatasources) GetDataSource(ctx context.Context, uid string) (*v0alpha1.GenericDataSource, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ListDataSource implements PluginDatasourceProvider.
|
||||
func (m mockDatasources) ListDataSource(ctx context.Context) (*v0alpha1.GenericDataSourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// Get gets a specific datasource (that the user in context can see)
|
||||
func (m mockDatasources) Get(ctx context.Context, uid string) (*v0alpha1.DataSourceConnection, error) {
|
||||
return nil, nil
|
||||
|
||||
Reference in New Issue
Block a user