Make interface for webhook predicates more specific

Signed-off-by: Max Smythe <smythe@google.com>
This commit is contained in:
Max Smythe 2022-10-25 16:34:06 -07:00
parent b4ee0c0574
commit 00ebe0bf62
5 changed files with 28 additions and 15 deletions

View File

@ -22,12 +22,19 @@ import (
"k8s.io/api/admissionregistration/v1" "k8s.io/api/admissionregistration/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
webhookutil "k8s.io/apiserver/pkg/util/webhook" webhookutil "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/client-go/rest" "k8s.io/client-go/rest"
) )
// WebhookAccessor provides a common interface to both mutating and validating webhook types. // WebhookAccessor provides a common interface to both mutating and validating webhook types.
type WebhookAccessor interface { type WebhookAccessor interface {
// This accessor provides the methods needed to support matching against webhook
// predicates
namespace.NamespaceSelectorProvider
object.ObjectSelectorProvider
// GetUID gets a string that uniquely identifies the webhook. // GetUID gets a string that uniquely identifies the webhook.
GetUID() string GetUID() string
@ -36,10 +43,6 @@ type WebhookAccessor interface {
// GetRESTClient gets the webhook client // GetRESTClient gets the webhook client
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error) GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
// GetParsedNamespaceSelector gets the webhook NamespaceSelector field.
GetParsedNamespaceSelector() (labels.Selector, error)
// GetParsedObjectSelector gets the webhook ObjectSelector field.
GetParsedObjectSelector() (labels.Selector, error)
// GetName gets the webhook Name field. Note that the name is scoped to the webhook // GetName gets the webhook Name field. Note that the name is scoped to the webhook
// configuration and does not provide a globally unique identity, if a unique identity is // configuration and does not provide a globally unique identity, if a unique identity is

View File

@ -26,11 +26,15 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
utilerrors "k8s.io/apimachinery/pkg/util/errors" utilerrors "k8s.io/apimachinery/pkg/util/errors"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
clientset "k8s.io/client-go/kubernetes" clientset "k8s.io/client-go/kubernetes"
corelisters "k8s.io/client-go/listers/core/v1" corelisters "k8s.io/client-go/listers/core/v1"
) )
type NamespaceSelectorProvider interface {
// GetNamespaceSelector gets the webhook NamespaceSelector field.
GetParsedNamespaceSelector() (labels.Selector, error)
}
// Matcher decides if a request is exempted by the NamespaceSelector of a // Matcher decides if a request is exempted by the NamespaceSelector of a
// webhook configuration. // webhook configuration.
type Matcher struct { type Matcher struct {
@ -87,7 +91,7 @@ func (m *Matcher) GetNamespaceLabels(attr admission.Attributes) (map[string]stri
// MatchNamespaceSelector decideds whether the request matches the // MatchNamespaceSelector decideds whether the request matches the
// namespaceSelctor of the webhook. Only when they match, the webhook is called. // namespaceSelctor of the webhook. Only when they match, the webhook is called.
func (m *Matcher) MatchNamespaceSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) { func (m *Matcher) MatchNamespaceSelector(p NamespaceSelectorProvider, attr admission.Attributes) (bool, *apierrors.StatusError) {
namespaceName := attr.GetNamespace() namespaceName := attr.GetNamespace()
if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" { if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" {
// If the request is about a cluster scoped resource, and it is not a // If the request is about a cluster scoped resource, and it is not a
@ -96,7 +100,7 @@ func (m *Matcher) MatchNamespaceSelector(h webhook.WebhookAccessor, attr admissi
// Also update the comment in types.go // Also update the comment in types.go
return true, nil return true, nil
} }
selector, err := h.GetParsedNamespaceSelector() selector, err := p.GetParsedNamespaceSelector()
if err != nil { if err != nil {
return false, apierrors.NewInternalError(err) return false, apierrors.NewInternalError(err)
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package namespace package namespace_test
import ( import (
"reflect" "reflect"
@ -28,6 +28,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
) )
type fakeNamespaceLister struct { type fakeNamespaceLister struct {
@ -100,7 +101,7 @@ func TestGetNamespaceLabels(t *testing.T) {
expectedLabels: namespace1Labels, expectedLabels: namespace1Labels,
}, },
} }
matcher := Matcher{ matcher := namespace.Matcher{
NamespaceLister: namespaceLister, NamespaceLister: namespaceLister,
} }
for _, tt := range tests { for _, tt := range tests {
@ -119,7 +120,7 @@ func TestNotExemptClusterScopedResource(t *testing.T) {
NamespaceSelector: &metav1.LabelSelector{}, NamespaceSelector: &metav1.LabelSelector{},
} }
attr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "mock-name", schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "", admission.Create, &metav1.CreateOptions{}, false, nil) attr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "mock-name", schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "", admission.Create, &metav1.CreateOptions{}, false, nil)
matcher := Matcher{} matcher := namespace.Matcher{}
matches, err := matcher.MatchNamespaceSelector(webhook.NewValidatingWebhookAccessor("mock-hook", "mock-cfg", hook), attr) matches, err := matcher.MatchNamespaceSelector(webhook.NewValidatingWebhookAccessor("mock-hook", "mock-cfg", hook), attr)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)

View File

@ -22,10 +22,14 @@ import (
"k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/klog/v2" "k8s.io/klog/v2"
) )
type ObjectSelectorProvider interface {
// GetObjectSelector gets the webhook ObjectSelector field.
GetParsedObjectSelector() (labels.Selector, error)
}
// Matcher decides if a request selected by the ObjectSelector. // Matcher decides if a request selected by the ObjectSelector.
type Matcher struct { type Matcher struct {
} }
@ -45,8 +49,8 @@ func matchObject(obj runtime.Object, selector labels.Selector) bool {
// MatchObjectSelector decideds whether the request matches the ObjectSelector // MatchObjectSelector decideds whether the request matches the ObjectSelector
// of the webhook. Only when they match, the webhook is called. // of the webhook. Only when they match, the webhook is called.
func (m *Matcher) MatchObjectSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) { func (m *Matcher) MatchObjectSelector(p ObjectSelectorProvider, attr admission.Attributes) (bool, *apierrors.StatusError) {
selector, err := h.GetParsedObjectSelector() selector, err := p.GetParsedObjectSelector()
if err != nil { if err != nil {
return false, apierrors.NewInternalError(err) return false, apierrors.NewInternalError(err)
} }

View File

@ -14,7 +14,7 @@ See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
*/ */
package object package object_test
import ( import (
"testing" "testing"
@ -25,6 +25,7 @@ import (
"k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission" "k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
) )
func TestObjectSelector(t *testing.T) { func TestObjectSelector(t *testing.T) {
@ -51,7 +52,7 @@ func TestObjectSelector(t *testing.T) {
}, },
}, },
} }
matcher := &Matcher{} matcher := &object.Matcher{}
allScopes := v1.AllScopes allScopes := v1.AllScopes
testcases := []struct { testcases := []struct {
name string name string