with configs

This commit is contained in:
Ryan McKinley
2025-06-19 17:27:40 +03:00
parent 7135832fff
commit 0bff610506
11 changed files with 596 additions and 41 deletions
+57
View File
@@ -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"`
}
+28 -1
View File
@@ -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
+79
View File
@@ -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)
}
+49 -24
View File
@@ -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
}
+2 -1
View File
@@ -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)
+27 -12
View File
@@ -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,
+16 -3
View File
@@ -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