diff --git a/pkg/registry/apis/collections/datasources_validator.go b/pkg/registry/apis/collections/datasources_validator.go index 187523b1335..7e8eab7d255 100644 --- a/pkg/registry/apis/collections/datasources_validator.go +++ b/pkg/registry/apis/collections/datasources_validator.go @@ -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 +} diff --git a/pkg/registry/apis/collections/register.go b/pkg/registry/apis/collections/register.go index bf812c97268..a53481d826c 100644 --- a/pkg/registry/apis/collections/register.go +++ b/pkg/registry/apis/collections/register.go @@ -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}, diff --git a/pkg/server/wire_gen.go b/pkg/server/wire_gen.go index d57273ccd35..9baae3adde6 100644 --- a/pkg/server/wire_gen.go +++ b/pkg/server/wire_gen.go @@ -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) diff --git a/pkg/services/datasources/service/client/client.go b/pkg/services/datasources/service/client/client.go new file mode 100644 index 00000000000..9e3d460b6e6 --- /dev/null +++ b/pkg/services/datasources/service/client/client.go @@ -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, +// } + +// } +// }