<commit_before>package validators

import (
	"context"
	"reflect"

	"github.com/go-logr/logr"
	admissionv1beta1 "k8s.io/api/admission/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

	api "sigs.k8s.io/multi-tenancy/incubator/hnc/api/v1alpha1"
	"sigs.k8s.io/multi-tenancy/incubator/hnc/internal/config"
	"sigs.k8s.io/multi-tenancy/incubator/hnc/internal/forest"
	"sigs.k8s.io/multi-tenancy/incubator/hnc/internal/metadata"
	"sigs.k8s.io/multi-tenancy/incubator/hnc/internal/object"
)

// ObjectsServingPath is where the validator will run. Must be kept in sync with the
// kubebuilder markers below.
const (
	ObjectsServingPath = "/validate-objects"
)

// Note: the validating webhook FAILS OPEN. This means that if the webhook goes down, all further
// changes to the objects are allowed.
//
// +kubebuilder:webhook:path=/validate-objects,mutating=false,failurePolicy=ignore,groups="*",resources="*",verbs=create;update;delete,versions="*",name=objects.hnc.x-k8s.io

type Object struct {
	Log     logr.Logger
	Forest  *forest.Forest
	client  client.Client
	decoder *admission.Decoder
}

func (o *Object) Handle(ctx context.Context, req admission.Request) admission.Response {
	log := o.Log.WithValues("nm", req.Name, "resource", req.Resource, "nnm", req.Namespace, "op", req.Operation)

	// Before even looking at the objects, early-exit for any changes we shouldn't be involved in.
	// This reduces the chance we'll hose some aspect of the cluster we weren't supposed to touch.
	//
	// Firstly, skip namespaces we're excluded from (like kube-system).
	if config.EX[req.Namespace] {
		return allow("excluded namespace " + req.Namespace)
	}
	// Allow changes to the types that are not in propagate mode. This is to dynamically enable/disable
	// object webhooks based on the types configured in hncconfig. Since the current admission rules only
	// apply to propagated objects, we can disable object webhooks on all other non-propagate-mode types.
	if !o.isPropagateType(req.Kind) {
		return allow("Non-propagate-mode types")
	}
	// Finally, let the HNC SA do whatever it wants.
	if isHNCServiceAccount(&req.AdmissionRequest.UserInfo) {
		log.V(1).Info("Allowed change by HNC SA")
		return allow("HNC SA")
	}

	// Decode the old and new object, if we expect them to exist ("old" won't exist for creations,
	// while "new" won't exist for deletions).
	inst := &unstructured.Unstructured{}
	oldInst := &unstructured.Unstructured{}
	if req.Operation != admissionv1beta1.Delete {
		if err := o.decoder.Decode(req, inst); err != nil {
			log.Error(err, "Couldn't decode req.Object", "raw", req.Object)
			return deny(metav1.StatusReasonBadRequest, err.Error())
		}
	}
	if req.Operation != admissionv1beta1.Create {
		if err := o.decoder.DecodeRaw(req.OldObject, oldInst); err != nil {
			log.Error(err, "Couldn't decode req.OldObject", "raw", req.OldObject)
			return deny(metav1.StatusReasonBadRequest, err.Error())
		}
	}

	// Run the actual logic.
	resp := o.handle(ctx, log, req.Operation, inst, oldInst)

	level := 1
	if !resp.Allowed {
		level = 0
	}
	log.V(level).Info("Handled", "allowed", resp.Allowed, "code", resp.Result.Code, "reason", resp.Result.Reason, "message", resp.Result.Message)
	return resp
}

func (o *Object) isPropagateType(gvk metav1.GroupVersionKind) bool {
	o.Forest.Lock()
	defer o.Forest.Unlock()

	ts := o.Forest.GetTypeSyncerFromGroupKind(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind})
	return ts != nil && ts.GetMode() == api.Propagate<commit_msg>Unbreak object validator on 1.14<commit_after>package validators

import (
	"context"
	"reflect"

	"github.com/go-logr/logr"
	admissionv1beta1 "k8s.io/api/admission/v1beta1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
	"k8s.io/apimachinery/pkg/runtime/schema"
	"sigs.k8s.io/controller-runtime/pkg/client"
	"sigs.k8s.io/controller-runtime/pkg/webhook/admission"

	api "sigs.k8s.io/multi-tenancy/incubator/hnc/api/v1alpha1"
	"sigs.k8s.io/multi-tenancy/incubator/hnc/internal/config"
	"sigs.k8s.io/multi-tenancy/incubator/hnc/internal/forest"
	"sigs.k8s.io/multi-tenancy/incubator/hnc/internal/metadata"
	"sigs.k8s.io/multi-tenancy/incubator/hnc/internal/object"
)

// ObjectsServingPath is where the validator will run. Must be kept in sync with the
// kubebuilder markers below.
const (
	ObjectsServingPath = "/validate-objects"
)

// Note: the validating webhook FAILS OPEN. This means that if the webhook goes down, all further
// changes to the objects are allowed.
//
// +kubebuilder:webhook:path=/validate-objects,mutating=false,failurePolicy=ignore,groups="*",resources="*",verbs=create;update;delete,versions="*",name=objects.hnc.x-k8s.io

type Object struct {
	Log     logr.Logger
	Forest  *forest.Forest
	client  client.Client
	decoder *admission.Decoder
}

func (o *Object) Handle(ctx context.Context, req admission.Request) admission.Response {
	log := o.Log.WithValues("nm", req.Name, "resource", req.Resource, "nnm", req.Namespace, "op", req.Operation)

	// Before even looking at the objects, early-exit for any changes we shouldn't be involved in.
	// This reduces the chance we'll hose some aspect of the cluster we weren't supposed to touch.
	//
	// Firstly, skip namespaces we're excluded from (like kube-system).
	if config.EX[req.Namespace] {
		return allow("excluded namespace " + req.Namespace)
	}
	// Allow changes to the types that are not in propagate mode. This is to dynamically enable/disable
	// object webhooks based on the types configured in hncconfig. Since the current admission rules only
	// apply to propagated objects, we can disable object webhooks on all other non-propagate-mode types.
	if !o.isPropagateType(req.Kind) {
		return allow("Non-propagate-mode types")
	}
	// Finally, let the HNC SA do whatever it wants.
	if isHNCServiceAccount(&req.AdmissionRequest.UserInfo) {
		log.V(1).Info("Allowed change by HNC SA")
		return allow("HNC SA")
	}

	// Decode the old and new object, if we expect them to exist ("old" won't exist for creations,
	// while "new" won't exist for deletions).
	inst := &unstructured.Unstructured{}
	oldInst := &unstructured.Unstructured{}
	if req.Operation != admissionv1beta1.Delete {
		if err := o.decoder.Decode(req, inst); err != nil {
			log.Error(err, "Couldn't decode req.Object", "raw", req.Object)
			return deny(metav1.StatusReasonBadRequest, err.Error())
		}
	}
	if req.Operation != admissionv1beta1.Create {
		// See issue #688 and #889
		if req.Operation == admissionv1beta1.Delete && req.OldObject.Raw == nil {
			return allow("cannot validate deletions in K8s 1.14")
		}
		if err := o.decoder.DecodeRaw(req.OldObject, oldInst); err != nil {
			log.Error(err, "Couldn't decode req.OldObject", "raw", req.OldObject)
			return deny(metav1.StatusReasonBadRequest, err.Error())
		}
	}

	// Run the actual logic.
	resp := o.handle(ctx, log, req.Operation, inst, oldInst)

	level := 1
	if !resp.Allowed {
		level = 0
	}
	log.V(level).Info("Handled", "allowed", resp.Allowed, "code", resp.Result.Code, "reason", resp.Result.Reason, "message", resp.Result.Message)
	return resp
}

func (o *Object) isPropagateType(gvk metav1.GroupVersionKind) bool {
	o.Forest.Lock()
	defer o.Forest.Unlock()

	ts := o.Forest.GetTypeSyncerFromGroupKind(schema.GroupKind{Group: gvk.Group, Kind: gvk.Kind})
	return ts != nil && ts.GetMode() == api.Propagate
{'fraction_non_alphanumeric': 0.13322738710104623, 'fraction_numerical': 0.01019732485763475, 'mean_word_length': 4.3446567586694975, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 3, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6428659', 'n_tokens_mistral': 2747, 'n_tokens_neox': 2627, 'n_words': 648}