Plugins: Support Admission validation hooks (#87718)

This commit is contained in:
Ryan McKinley
2024-05-24 18:45:16 +03:00
committed by GitHub
parent b1eb4b7dad
commit ffc2702552
43 changed files with 1091 additions and 117 deletions
@@ -0,0 +1,87 @@
package testdatasource
import (
"context"
"encoding/json"
"fmt"
"net/http"
"github.com/grafana/grafana-plugin-sdk-go/backend"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
// ValidateAdmission implements backend.AdmissionHandler.
func (s *Service) ValidateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.ValidationResponse, error) {
rsp, err := s.MutateAdmission(ctx, req)
if rsp != nil {
return &backend.ValidationResponse{
Allowed: rsp.Allowed,
Result: rsp.Result,
Warnings: rsp.Warnings,
}, err
}
return nil, err
}
// MutateAdmission implements backend.AdmissionHandler.
func (s *Service) MutateAdmission(ctx context.Context, req *backend.AdmissionRequest) (*backend.MutationResponse, error) {
expected := (&backend.DataSourceInstanceSettings{}).GVK()
if req.Kind.Kind != expected.Kind && req.Kind.Group != expected.Group {
return getBadRequest("expected DataSourceInstanceSettings protobuf payload"), nil
}
// Convert the payload from protobuf to an SDK struct
settings, err := backend.DataSourceInstanceSettingsFromProto(req.ObjectBytes, "")
if err != nil {
return nil, err
}
if settings == nil {
return getBadRequest("missing datasource settings"), nil
}
switch settings.APIVersion {
case "", "v0alpha1":
// OK!
default:
return getBadRequest(fmt.Sprintf("expected apiVersion: v0alpha1, found: %s", settings.APIVersion)), nil
}
if settings.JSONData != nil {
anything := map[string]any{}
err := json.Unmarshal(settings.JSONData, &anything)
if err != nil || len(anything) > 0 {
return getBadRequest("Expected empty jsonData settings"), nil
}
}
if len(settings.DecryptedSecureJSONData) > 0 {
return getBadRequest("found unsupported secure json fields"), nil
}
if settings.URL != "" {
return getBadRequest("unsupported URL value"), nil
}
if settings.User != "" {
return getBadRequest("unsupported User value"), nil
}
pb, err := backend.DataSourceInstanceSettingsToProtoBytes(settings)
return &backend.MutationResponse{
Allowed: true,
ObjectBytes: pb,
}, err
}
// ConvertObject implements backend.AdmissionHandler.
func (s *Service) ConvertObject(ctx context.Context, req *backend.ConversionRequest) (*backend.ConversionResponse, error) {
return nil, fmt.Errorf("not implemented")
}
func getBadRequest(msg string) *backend.MutationResponse {
return &backend.MutationResponse{
Allowed: false,
Result: &backend.StatusResult{
Status: "Failure",
Message: msg,
Reason: string(metav1.StatusReasonBadRequest),
Code: http.StatusBadRequest,
},
}
}
@@ -0,0 +1,68 @@
package testdatasource
import (
"context"
"encoding/json"
"testing"
"github.com/grafana/grafana-plugin-sdk-go/backend"
"github.com/stretchr/testify/require"
)
func TestSettingsHandler(t *testing.T) {
svc := &Service{}
require.NotNil(t, svc)
// Check missing datasource
s, err := svc.MutateAdmission(context.Background(), &backend.AdmissionRequest{
PluginContext: backend.PluginContext{},
})
require.NoError(t, err)
require.False(t, s.Allowed)
require.Equal(t, int32(400), s.Result.Code)
// Empty is OK
s, _ = svc.MutateAdmission(context.Background(),
asAdmissionRequest(&backend.DataSourceInstanceSettings{
APIVersion: "v0alpha1",
}))
require.True(t, s.Allowed)
// Any values should be an error
s, err = svc.MutateAdmission(context.Background(),
asAdmissionRequest(&backend.DataSourceInstanceSettings{
JSONData: json.RawMessage(`{"hello": "world"}`), // Settings must be empty
}))
require.NoError(t, err)
require.False(t, s.Allowed)
require.Equal(t, int32(400), s.Result.Code)
// Any values should be an error
s, err = svc.MutateAdmission(context.Background(),
asAdmissionRequest(&backend.DataSourceInstanceSettings{
DecryptedSecureJSONData: map[string]string{
"A": "Value",
},
}))
require.NoError(t, err)
require.False(t, s.Allowed)
require.Equal(t, int32(400), s.Result.Code)
// Invalid API Version
s, err = svc.MutateAdmission(context.Background(),
asAdmissionRequest(&backend.DataSourceInstanceSettings{
APIVersion: "v1234",
}))
require.NoError(t, err)
require.False(t, s.Allowed)
require.Equal(t, int32(400), s.Result.Code)
}
func asAdmissionRequest(settings *backend.DataSourceInstanceSettings) *backend.AdmissionRequest {
req := &backend.AdmissionRequest{}
if settings != nil {
req.Kind = settings.GVK()
req.ObjectBytes, _ = backend.DataSourceInstanceSettingsToProtoBytes(settings)
}
return req
}
@@ -53,13 +53,17 @@
"type"
],
"properties": {
"apiVersion": {
"description": "The apiserver version",
"type": "string"
},
"type": {
"description": "The datasource plugin type",
"type": "string",
"pattern": "^grafana-testdata-datasource$|^testdata$"
},
"uid": {
"description": "Datasource UID",
"description": "Datasource UID (NOTE: name in k8s)",
"type": "string"
}
},
@@ -63,13 +63,17 @@
"type"
],
"properties": {
"apiVersion": {
"description": "The apiserver version",
"type": "string"
},
"type": {
"description": "The datasource plugin type",
"type": "string",
"pattern": "^grafana-testdata-datasource$|^testdata$"
},
"uid": {
"description": "Datasource UID",
"description": "Datasource UID (NOTE: name in k8s)",
"type": "string"
}
},
@@ -47,6 +47,13 @@ func ProvideService() *Service {
return s
}
var (
_ backend.QueryDataHandler = (*Service)(nil)
_ backend.CallResourceHandler = (*Service)(nil)
_ backend.AdmissionHandler = (*Service)(nil)
_ backend.CollectMetricsHandler = (*Service)(nil)
)
type Service struct {
logger log.Logger
scenarios map[kinds.TestDataQueryType]*Scenario
+3 -1
View File
@@ -12,6 +12,7 @@ import (
"github.com/grafana/grafana/pkg/infra/localcache"
"github.com/grafana/grafana/pkg/infra/log"
"github.com/grafana/grafana/pkg/plugins"
pluginfakes "github.com/grafana/grafana/pkg/plugins/manager/fakes"
acmock "github.com/grafana/grafana/pkg/services/accesscontrol/mock"
"github.com/grafana/grafana/pkg/services/datasources"
"github.com/grafana/grafana/pkg/services/datasources/guardian"
@@ -48,7 +49,8 @@ func TestHandleRequest(t *testing.T) {
datasourcePermissions := acmock.NewMockedPermissionsService()
quotaService := quotatest.New(false, nil)
dsCache := datasourceservice.ProvideCacheService(localcache.ProvideService(), sqlStore, guardian.ProvideGuardian())
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(), acmock.New(), datasourcePermissions, quotaService, &pluginstore.FakePluginStore{})
dsService, err := datasourceservice.ProvideService(nil, secretsService, secretsStore, cfg, featuremgmt.WithFeatures(),
acmock.New(), datasourcePermissions, quotaService, &pluginstore.FakePluginStore{}, &pluginfakes.FakePluginClient{})
require.NoError(t, err)
pCtxProvider := plugincontext.ProvideService(cfg, localcache.ProvideService(), &pluginstore.FakePluginStore{