wip: basic logic to check that datasource exists in validation

This commit is contained in:
Dafydd
2025-12-04 12:34:14 +00:00
parent fd31f087ee
commit 90c4ab9d96
4 changed files with 145 additions and 7 deletions
@@ -3,18 +3,23 @@ package collections
import (
"context"
"fmt"
"net/http"
collections "github.com/grafana/grafana/apps/collections/pkg/apis/collections/v1alpha1"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"k8s.io/apiserver/pkg/admission"
"k8s.io/client-go/kubernetes"
)
var _ builder.APIGroupValidation = (*DatasourceStacksValidator)(nil)
type DatasourceStacksValidator struct{}
type DatasourceStacksValidator struct {
restConfigProvider apiserver.RestConfigProvider
}
func GetDatasourceStacksValidator() builder.APIGroupValidation {
return &DatasourceStacksValidator{}
func GetDatasourceStacksValidator(restConfigProvider apiserver.RestConfigProvider) builder.APIGroupValidation {
return &DatasourceStacksValidator{restConfigProvider: restConfigProvider}
}
func (v *DatasourceStacksValidator) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
@@ -54,13 +59,54 @@ func (v *DatasourceStacksValidator) Validate(ctx context.Context, a admission.At
modes := cast.Spec.Modes
for _, mode := range modes {
for key := range mode.Definition {
for key, item := range mode.Definition {
// if a key is not in the template, return an error
if _, ok := template[key]; !ok {
return fmt.Errorf("key '%s' is not in the DataSourceStack template (%s %s)", key, a.GetName(), a.GetKind().GroupVersion().String())
}
exists, err := v.checkDatasourceExists(ctx, template[key].Group, item.DataSourceRef)
if err != nil {
return fmt.Errorf("error fetching: datasource '%s' does not exist (%s %s): %w", item.DataSourceRef, a.GetName(), a.GetKind().GroupVersion().String(), err)
}
if !exists {
return fmt.Errorf("datasource '%s' does not exist (%s %s)", item.DataSourceRef, a.GetName(), a.GetKind().GroupVersion().String())
}
}
}
return nil
}
func (v *DatasourceStacksValidator) checkDatasourceExists(ctx context.Context, group, name string) (bool, error) {
cfg, err := v.restConfigProvider.GetRestConfig(ctx)
if err != nil {
return false, err
}
client, err := kubernetes.NewForConfig(cfg)
if err != nil {
return false, err
}
result := client.RESTClient().Get().
Prefix("apis", group, "v0alpha1").
Namespace("default").
Resource("datasources").
Name(name).
Do(ctx)
if err = result.Error(); err != nil {
return false, err
}
var statusCode int
result = result.StatusCode(&statusCode)
if statusCode == http.StatusNotFound {
return false, nil
}
return true, nil
}
+3 -1
View File
@@ -22,6 +22,7 @@ import (
"github.com/grafana/grafana/pkg/infra/db"
"github.com/grafana/grafana/pkg/registry/apis/collections/legacy"
"github.com/grafana/grafana/pkg/registry/apis/preferences/utils"
"github.com/grafana/grafana/pkg/services/apiserver"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/grafana/grafana/pkg/services/apiserver/endpoints/request"
"github.com/grafana/grafana/pkg/services/featuremgmt"
@@ -50,6 +51,7 @@ func RegisterAPIService(
stars star.Service,
users user.Service,
apiregistration builder.APIRegistrar,
restConfigProvider apiserver.RestConfigProvider,
) *APIBuilder {
// Requires development settings and clearly experimental
//nolint:staticcheck // not yet migrated to OpenFeature
@@ -59,7 +61,7 @@ func RegisterAPIService(
sql := legacy.NewLegacySQL(legacysql.NewDatabaseProvider(db))
builder := &APIBuilder{
datasourceStacksValidator: GetDatasourceStacksValidator(),
datasourceStacksValidator: GetDatasourceStacksValidator(restConfigProvider),
authorizer: &utils.AuthorizeFromName{
Resource: map[string][]utils.ResourceOwner{
"stars": {utils.UserResourceOwner},
+2 -2
View File
@@ -886,7 +886,7 @@ func Initialize(ctx context.Context, cfg *setting.Cfg, opts Options, apiOpts api
}
userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer)
apiBuilder := preferences.RegisterAPIService(cfg, featureToggles, sqlStore, prefService, userService, apiserverService)
collectionsAPIBuilder := collections.RegisterAPIService(cfg, featureToggles, sqlStore, starService, userService, apiserverService)
collectionsAPIBuilder := collections.RegisterAPIService(cfg, featureToggles, sqlStore, starService, userService, apiserverService, eventualRestConfigProvider)
webhookExtraBuilder := webhooks.ProvideWebhooksWithImages(cfg, renderingService, resourceClient, eventualRestConfigProvider, registerer)
v3 := extras.ProvideProvisioningExtraAPIs(webhookExtraBuilder)
pullRequestWorker := pullrequest.ProvidePullRequestWorker(cfg, renderingService, resourceClient, eventualRestConfigProvider, registerer)
@@ -1540,7 +1540,7 @@ func InitializeForTest(ctx context.Context, t sqlutil.ITestDB, testingT interfac
}
userStorageAPIBuilder := userstorage.RegisterAPIService(featureToggles, apiserverService, registerer)
apiBuilder := preferences.RegisterAPIService(cfg, featureToggles, sqlStore, prefService, userService, apiserverService)
collectionsAPIBuilder := collections.RegisterAPIService(cfg, featureToggles, sqlStore, starService, userService, apiserverService)
collectionsAPIBuilder := collections.RegisterAPIService(cfg, featureToggles, sqlStore, starService, userService, apiserverService, eventualRestConfigProvider)
webhookExtraBuilder := webhooks.ProvideWebhooksWithImages(cfg, renderingService, resourceClient, eventualRestConfigProvider, registerer)
v3 := extras.ProvideProvisioningExtraAPIs(webhookExtraBuilder)
pullRequestWorker := pullrequest.ProvidePullRequestWorker(cfg, renderingService, resourceClient, eventualRestConfigProvider, registerer)
@@ -0,0 +1,90 @@
package client
// import (
// "context"
// "encoding/json"
// "github.com/grafana/grafana/pkg/services/apiserver"
// "github.com/grafana/grafana/pkg/services/apiserver/client"
// v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
// "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
// "k8s.io/apimachinery/pkg/runtime/schema"
// "k8s.io/client-go/kubernetes"
// )
// // K8sClientFactory creates a K8sClient for the given group
// type K8sClientFactory func(ctx context.Context, group string, version string) client.K8sHandler
// type K8sHandler struct {
// client.K8sHandler
// restConfigProvider apiserver.RestConfigProvider
// gvr schema.GroupVersionResource
// }
// type K8sClient struct {
// client.K8sHandler
// newClientFunc K8sClientFactory
// }
// func ProvideK8sClient(
// restConfigProvider apiserver.RestConfigProvider,
// ) K8sHandler {
// return NewK8sClient(restConfigProvider)
// }
// func NewK8sClient(restConfigProvider apiserver.RestConfigProvider) *K8sClient {
// newClientFunc := newK8sClientFactory(restConfigProvider)
// return &K8sClient{
// K8sHandler: newClientFunc(context.Background(),),
// }
// }
// func (c K8sHandler) Get(ctx context.Context, name string, orgID int64, options v1.GetOptions, subresource ...string) (*unstructured.Unstructured, error) {
// cfg, err := c.restConfigProvider.GetRestConfig(ctx)
// if err != nil {
// return nil, err
// }
// client, err := kubernetes.NewForConfig(cfg)
// if err != nil {
// return nil, err
// }
// result := client.RESTClient().Get().
// Prefix("apis", c.gvr.Group, c.gvr.Version).
// Namespace(string(orgID)).
// Resource(c.gvr.Resource).
// Name(name).
// Do(ctx)
// if err = result.Error(); err != nil {
// return nil, err
// }
// body, err := result.Raw()
// if err != nil {
// return nil, err
// }
// value := &unstructured.Unstructured{}
// if err = json.Unmarshal(body, value); err != nil {
// return nil, err
// }
// return value, nil
// }
// func newK8sClientFactory(restConfigProvider apiserver.RestConfigProvider) K8sClientFactory {
// return func(ctx context.Context, group string, version string) client.K8sHandler {
// gvr := schema.GroupVersionResource{
// Group: group,
// Version: version,
// Resource: "datasources",
// }
// return K8sHandler{
// restConfigProvider: restConfigProvider,
// gvr: gvr,
// }
// }
// }