Plugins: Support Admission validation hooks (#87718)
This commit is contained in:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user