Files
grafana/pkg/services/apiserver/builder/admission_test.go
2025-02-14 12:29:43 -05:00

233 lines
7.2 KiB
Go

package builder_test
import (
"context"
"errors"
"testing"
"github.com/grafana/grafana/pkg/services/apiserver/builder"
"github.com/stretchr/testify/require"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/apis/example"
)
func TestBuilderAdmission_Validate(t *testing.T) {
gvk := schema.GroupVersionKind{
Group: "example.grafana.app",
Version: "v1",
Kind: "Foo",
}
gvr := gvk.GroupVersion().WithResource("foos")
defaultAttributes := admission.NewAttributesRecord(nil, nil, gvk, "default", "foo", 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{
gvk.GroupVersion(): &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{
gvk.GroupVersion(): &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{},
attributes: defaultAttributes,
wantErr: false,
},
{
name: "multiple validators",
validators: map[schema.GroupVersion]builder.APIGroupValidation{
{Group: "example.grafana.app", Version: "v1beta"}: &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
},
},
gvk.GroupVersion(): &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return admission.NewForbidden(a, errors.New("test error"))
},
},
{Group: "example.grafana.app", Version: "v2"}: &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
},
},
},
attributes: defaultAttributes,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := builder.NewAdmission(nil, tt.validators)
err := b.Validate(context.Background(), tt.attributes, nil)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
})
}
}
func TestNewAdmissionFromBuilders(t *testing.T) {
gvk := schema.GroupVersionKind{
Group: "example.grafana.app",
Version: "v1",
Kind: "Foo",
}
gvr := gvk.GroupVersion().WithResource("foos")
defaultAttributes := admission.NewAttributesRecord(nil, nil, gvk, "default", "foo", gvr, "", admission.Create, nil, false, nil)
builders := []builder.APIGroupBuilder{
&mockBuilder{
groupVersion: schema.GroupVersion{Group: "example.grafana.app", Version: "v1beta"},
validator: &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
},
},
},
&mockBuilder{
groupVersion: gvk.GroupVersion(),
validator: &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return admission.NewForbidden(a, errors.New("test error"))
},
},
},
&mockBuilder{
groupVersion: schema.GroupVersion{Group: "example.grafana.app", Version: "v2"},
validator: &mockValidator{
validateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return nil
},
},
},
}
a := builder.NewAdmissionFromBuilders(builders)
err := a.Validate(context.Background(), defaultAttributes, nil)
require.Error(t, err)
}
func TestBuilderAdmission_Admit(t *testing.T) {
gvk := schema.GroupVersionKind{
Group: "test.grafana.app",
Version: "v1",
Kind: "Foo",
}
gvr := gvk.GroupVersion().WithResource("foos")
exampleObj := &example.Pod{ObjectMeta: metav1.ObjectMeta{Name: "foo"}, Spec: example.PodSpec{}}
tests := []struct {
name string
mutators map[schema.GroupVersion]builder.APIGroupMutation
attributes admission.Attributes
wantErr bool
wantSpec example.PodSpec
}{
{
name: "mutator exists - error",
mutators: map[schema.GroupVersion]builder.APIGroupMutation{
gvk.GroupVersion(): &mockMutator{
mutateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return errors.New("test error")
},
},
},
attributes: admission.NewAttributesRecord(exampleObj.DeepCopy(), nil, gvk, "default", "foo", gvr, "", admission.Create, nil, false, nil),
wantErr: true,
wantSpec: exampleObj.Spec,
},
{
name: "mutator exists - add hostname",
mutators: map[schema.GroupVersion]builder.APIGroupMutation{
gvk.GroupVersion(): &mockMutator{
mutateFunc: func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
obj := a.GetObject().(*example.Pod)
obj.Spec.Hostname = "test"
return nil
},
},
},
attributes: admission.NewAttributesRecord(exampleObj.DeepCopy(), nil, gvk, "default", "foo", gvr, "", admission.Create, nil, false, nil),
wantErr: false,
wantSpec: example.PodSpec{Hostname: "test"},
},
{
name: "mutator does not exist",
mutators: map[schema.GroupVersion]builder.APIGroupMutation{},
attributes: admission.NewAttributesRecord(exampleObj.DeepCopy(), nil, gvk, "default", "foo", gvr, "", admission.Create, nil, false, nil),
wantErr: false,
wantSpec: exampleObj.Spec,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := builder.NewAdmission(tt.mutators, nil)
err := b.Admit(context.Background(), tt.attributes, nil)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
}
require.Equal(t, tt.wantSpec, tt.attributes.GetObject().(*example.Pod).Spec)
})
}
}
type mockBuilder struct {
builder.APIGroupBuilder
groupVersion schema.GroupVersion
validator builder.APIGroupValidation
}
func (m *mockBuilder) GetGroupVersions() []schema.GroupVersion {
return []schema.GroupVersion{m.groupVersion}
}
func (m *mockBuilder) Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return m.validator.Validate(ctx, a, o)
}
type mockMutator struct {
mutateFunc func(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error
}
func (m *mockMutator) Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) error {
return m.mutateFunc(ctx, a, o)
}
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)
}