K8s: Add admission mutation support to builder (#95526)

This commit is contained in:
Todd Treece 2024-10-29 02:14:07 +01:00 committed by GitHub
parent 4508d6c143
commit c8238c7914
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 101 additions and 3 deletions

View File

@ -8,11 +8,20 @@ import (
)
type builderAdmission struct {
mutators map[schema.GroupVersion]APIGroupMutation
validators map[schema.GroupVersion]APIGroupValidation
}
var _ admission.MutationInterface = (*builderAdmission)(nil)
var _ admission.ValidationInterface = (*builderAdmission)(nil)
func (b *builderAdmission) Admit(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error) {
if m, ok := b.mutators[a.GetResource().GroupVersion()]; ok {
return m.Mutate(ctx, a, o)
}
return 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)
@ -25,17 +34,22 @@ func (b *builderAdmission) Handles(operation admission.Operation) bool {
}
func NewAdmissionFromBuilders(builders []APIGroupBuilder) *builderAdmission {
mutators := make(map[schema.GroupVersion]APIGroupMutation)
validators := make(map[schema.GroupVersion]APIGroupValidation)
for _, builder := range builders {
if m, ok := builder.(APIGroupMutation); ok {
mutators[builder.GetGroupVersion()] = m
}
if v, ok := builder.(APIGroupValidation); ok {
validators[builder.GetGroupVersion()] = v
}
}
return NewAdmission(validators)
return NewAdmission(mutators, validators)
}
func NewAdmission(validators map[schema.GroupVersion]APIGroupValidation) *builderAdmission {
func NewAdmission(mutators map[schema.GroupVersion]APIGroupMutation, validators map[schema.GroupVersion]APIGroupValidation) *builderAdmission {
return &builderAdmission{
mutators: mutators,
validators: validators,
}
}

View File

@ -7,8 +7,10 @@ import (
"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) {
@ -81,7 +83,7 @@ func TestBuilderAdmission_Validate(t *testing.T) {
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
b := builder.NewAdmission(tt.validators)
b := builder.NewAdmission(nil, tt.validators)
err := b.Validate(context.Background(), tt.attributes, nil)
if tt.wantErr {
require.Error(t, err)
@ -133,6 +135,72 @@ func TestNewAdmissionFromBuilders(t *testing.T) {
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
@ -147,6 +215,14 @@ func (m *mockBuilder) Validate(ctx context.Context, a admission.Attributes, o ad
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
}

View File

@ -47,7 +47,15 @@ type APIGroupBuilder interface {
GetAuthorizer() authorizer.Authorizer
}
type APIGroupMutation interface {
// Mutate allows the builder to make changes to the object before it is persisted.
// Context is used only for timeout/deadline/cancellation and tracing information.
Mutate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error)
}
type APIGroupValidation interface {
// Validate makes an admission decision based on the request attributes. It is NOT allowed to mutate
// Context is used only for timeout/deadline/cancellation and tracing information.
Validate(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces) (err error)
}