package query import ( "context" "fmt" "k8s.io/apimachinery/pkg/apis/meta/internalversion" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apiserver/pkg/endpoints/request" "k8s.io/apiserver/pkg/registry/rest" authlib "github.com/grafana/authlib/types" "github.com/grafana/grafana/pkg/apimachinery/utils" queryV0 "github.com/grafana/grafana/pkg/apis/query/v0alpha1" gapiutil "github.com/grafana/grafana/pkg/services/apiserver/utils" "github.com/grafana/grafana/pkg/services/datasources" ) var ( _ rest.Scoper = (*connectionAccess)(nil) _ rest.SingularNameProvider = (*connectionAccess)(nil) _ rest.Getter = (*connectionAccess)(nil) _ rest.Lister = (*connectionAccess)(nil) _ rest.Storage = (*connectionAccess)(nil) ) // Get all datasource connections -- this will be backed by search or duplicated resource in unified storage type DataSourceConnectionProvider interface { // Get gets a specific datasource (that the user in context can see) // The name is {group}:{name}, see /pkg/apis/query/v0alpha1/connection.go#L34 GetConnection(ctx context.Context, namespace string, name string) (*queryV0.DataSourceConnection, error) // List lists all data sources the user in context can see ListConnections(ctx context.Context, namespace string) (*queryV0.DataSourceConnectionList, error) } type connectionAccess struct { tableConverter rest.TableConvertor connections DataSourceConnectionProvider } func (s *connectionAccess) New() runtime.Object { return queryV0.ConnectionResourceInfo.NewFunc() } func (s *connectionAccess) Destroy() {} func (s *connectionAccess) NamespaceScoped() bool { return true } func (s *connectionAccess) GetSingularName() string { return queryV0.ConnectionResourceInfo.GetSingularName() } func (s *connectionAccess) ShortNames() []string { return queryV0.ConnectionResourceInfo.GetShortNames() } func (s *connectionAccess) NewList() runtime.Object { return queryV0.ConnectionResourceInfo.NewListFunc() } func (s *connectionAccess) ConvertToTable(ctx context.Context, object runtime.Object, tableOptions runtime.Object) (*metav1.Table, error) { if s.tableConverter == nil { s.tableConverter = queryV0.ConnectionResourceInfo.TableConverter() } return s.tableConverter.ConvertToTable(ctx, object, tableOptions) } func (s *connectionAccess) Get(ctx context.Context, name string, options *metav1.GetOptions) (runtime.Object, error) { return s.connections.GetConnection(ctx, request.NamespaceValue(ctx), name) } func (s *connectionAccess) List(ctx context.Context, options *internalversion.ListOptions) (runtime.Object, error) { return s.connections.ListConnections(ctx, request.NamespaceValue(ctx)) } type connectionsProvider struct { dsService datasources.DataSourceService registry queryV0.DataSourceApiServerRegistry } var ( _ DataSourceConnectionProvider = (*connectionsProvider)(nil) ) func (q *connectionsProvider) GetConnection(ctx context.Context, namespace string, name string) (*queryV0.DataSourceConnection, error) { info, err := authlib.ParseNamespace(namespace) if err != nil { return nil, err } ds, err := q.dsService.GetDataSource(ctx, &datasources.GetDataSourceQuery{ UID: name, OrgID: info.OrgID, }) if err != nil { return nil, err } // TODO... access control? return q.asConnection(ds, namespace) } func (q *connectionsProvider) ListConnections(ctx context.Context, namespace string) (*queryV0.DataSourceConnectionList, error) { ns, err := authlib.ParseNamespace(namespace) if err != nil { return nil, err } dss, err := q.dsService.GetDataSources(ctx, &datasources.GetDataSourcesQuery{ OrgID: ns.OrgID, DataSourceLimit: 10000, }) if err != nil { return nil, err } result := &queryV0.DataSourceConnectionList{ Items: []queryV0.DataSourceConnection{}, } for _, ds := range dss { v, err := q.asConnection(ds, namespace) if err != nil { return nil, err } result.Items = append(result.Items, *v) } return result, nil } func (q *connectionsProvider) asConnection(ds *datasources.DataSource, ns string) (v *queryV0.DataSourceConnection, err error) { gv, err := q.registry.GetDatasourceGroupVersion(ds.Type) if err != nil { return nil, fmt.Errorf("datasource type %q does not map to an apiserver %w", ds.Type, err) } v = &queryV0.DataSourceConnection{ ObjectMeta: metav1.ObjectMeta{ Name: queryV0.DataSourceConnectionName(gv.Group, ds.UID), Namespace: ns, CreationTimestamp: metav1.NewTime(ds.Created), ResourceVersion: fmt.Sprintf("%d", ds.Updated.UnixMilli()), Generation: int64(ds.Version), }, Title: ds.Name, Datasource: queryV0.DataSourceConnectionRef{ Group: gv.Group, Version: gv.Version, Name: ds.UID, }, } v.UID = gapiutil.CalculateClusterWideUID(v) // UID is unique across all groups if !ds.Updated.IsZero() { meta, err := utils.MetaAccessor(v) if err != nil { meta.SetUpdatedTimestamp(&ds.Updated) } } return v, err }