K8s: Add validation support to builder (#95502)

This commit is contained in:
Todd Treece 2024-10-28 16:40:25 +01:00 committed by GitHub
parent e0163c93c2
commit 5533b30135
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 142 additions and 2 deletions

View File

@ -8,6 +8,7 @@ import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/rest"
genericapiserver "k8s.io/apiserver/pkg/server"
@ -28,6 +29,7 @@ import (
)
var _ builder.APIGroupBuilder = (*FolderAPIBuilder)(nil)
var _ builder.APIGroupValidation = (*FolderAPIBuilder)(nil)
var resourceInfo = v0alpha1.FolderResourceInfo
@ -181,6 +183,10 @@ func (b *FolderAPIBuilder) GetAuthorizer() authorizer.Authorizer {
})
}
func (b *FolderAPIBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
}
func authorizerFunc(ctx context.Context, attr authorizer.Attributes) (*authorizerParams, error) {
verb := attr.GetVerb()
name := attr.GetName()

View File

@ -0,0 +1,43 @@
package builder
import (
"context"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
)
const PluginName = "GrafanaAdmission"
type builderAdmission struct {
validators map[schema.GroupVersion]APIGroupValidation
}
var _ admission.ValidationInterface = (*builderAdmission)(nil)
func (b *builderAdmission) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
if v, ok := b.validators[a.GetResource().GroupVersion()]; ok {
return v.Validate(ctx, a, o)
}
return nil
}
func (b *builderAdmission) Handles(operation admission.Operation) bool {
return true
}
func NewAdmissionFromBuilders(builders []APIGroupBuilder) *builderAdmission {
validators := make(map[schema.GroupVersion]APIGroupValidation)
for _, builder := range builders {
if v, ok := builder.(APIGroupValidation); ok {
validators[builder.GetGroupVersion()] = v
}
}
return NewAdmission(validators)
}
func NewAdmission(validators map[schema.GroupVersion]APIGroupValidation) *builderAdmission {
return &builderAdmission{
validators: validators,
}
}

View File

@ -0,0 +1,85 @@
package builder_test
import (
"context"
"errors"
"testing"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/stretchr/testify/require"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
)
func TestBuilderAdmission_Validate(t *testing.T) {
gvk := schema.GroupVersionKind{
Group: "testGroup",
Version: "v1",
Kind: "testKind",
}
gvr := gvk.GroupVersion().WithResource("testkinds")
defaultAttributes := admission.NewAttributesRecord(nil, nil, gvk, "", "", gvr, "", admission.Create, nil, false, nil)
tests := []struct {
name string
validators map[schema.GroupVersion]builder.APIGroupValidation
attributes admission.Attributes
wantErr bool
}{
{
name: "validator exists - forbidden",
validators: map[schema.GroupVersion]builder.APIGroupValidation{
{Group: "testGroup", Version: "v1"}: &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return admission.NewForbidden(a, errors.New("test error"))
},
},
},
attributes: defaultAttributes,
wantErr: true,
},
{
name: "validator exists - allowed",
validators: map[schema.GroupVersion]builder.APIGroupValidation{
{Group: "testGroup", Version: "v1"}: &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
},
},
},
attributes: defaultAttributes,
wantErr: false,
},
{
name: "validator does not exist",
validators: map[schema.GroupVersion]builder.APIGroupValidation{
{Group: "testGroup", Version: "v1"}: &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
},
},
},
attributes: defaultAttributes,
wantErr: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := builder.NewAdmission(tt.validators)
err := b.Validate(context.Background(), tt.attributes, nil)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
type mockValidator struct {
validateFunc func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error
}
func (m *mockValidator) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return m.validateFunc(ctx, a, o)
}

View File

@ -1,18 +1,19 @@
package builder
import (
"context"
"net/http"
"github.com/prometheus/client_golang/prometheus"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/registry/generic"
genericapiserver "k8s.io/apiserver/pkg/server"
"k8s.io/kube-openapi/pkg/common"
"k8s.io/kube-openapi/pkg/spec3"
"github.com/prometheus/client_golang/prometheus"
grafanarest "github.com/grafana/grafana/pkg/apiserver/rest"
)
@ -46,6 +47,10 @@ type APIGroupBuilder interface {
GetAuthorizer() authorizer.Authorizer
}
type APIGroupValidation interface {
Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error)
}
type APIGroupOptions struct {
Scheme *runtime.Scheme
OptsGetter generic.RESTOptionsGetter

View File

@ -103,6 +103,7 @@ func SetupConfig(
buildBranch string,
buildHandlerChainFunc func(delegateHandler http.Handler, c *genericapiserver.Config) http.Handler,
) error {
serverConfig.AdmissionControl = NewAdmissionFromBuilders(builders)
defsGetter := GetOpenAPIDefinitions(builders)
serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig(
openapi.GetOpenAPIDefinitionsWithoutDisabledFeatures(defsGetter),