<commit_before>package webhookadmission

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/libopenstorage/stork/drivers/volume"
	"github.com/portworx/sched-ops/k8s/core"
	log "github.com/sirupsen/logrus"
	"k8s.io/api/admission/v1beta1"
	admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
	appv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	k8serr "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/record"
)

const (
	mutateWebHook            = "/mutate"
	validateWebHook          = "/validate"
	appSchedPrefix           = "/spec/template"
	podSpecSchedPath         = "/spec/schedulerName"
	storkScheduler           = "stork"
	storkAdmissionController = "stork-webhooks-cfg"
	secretName               = "servercert-secret"
	privKey                  = "privKey"
	privCert                 = "privCert"
)

// Controller for admission mutating webhook to initialise resources
// with stork as scheduler, if given resources are using driver supported
// by stork
type Controller struct {
	Recorder record.EventRecorder
	Driver   volume.Driver
	server   *http.Server
	lock     sync.Mutex
	started  bool
}

// Serve method for webhook server
func (c *Controller) serveHTTP(w http.ResponseWriter, req *http.Request) {
	if strings.Contains(req.URL.Path, mutateWebHook) {
		c.processMutateRequest(w, req)
	} else {
		http.Error(w, "Unsupported request", http.StatusNotFound)
	}
}

func (c *Controller) processMutateRequest(w http.ResponseWriter, req *http.Request) {
	var admissionResponse *v1beta1.AdmissionResponse
	var err error
	var schedPath string
	admissionReview := v1beta1.AdmissionReview{}
	isStorkResource := false

	webhookConfig := &admissionv1beta1.MutatingWebhookConfiguration{
		ObjectMeta: metav1.ObjectMeta{
			Name: storkAdmissionController,
		},
	}

	decoder := json.NewDecoder(req.Body)
	defer func() {
		if err := req.Body.Close(); err != nil {
			log.Warnf("Error closing decoder")
		}
	}()
	if err := decoder.Decode(&admissionReview); err != nil {
		log.Errorf("Error decoding admission review request: %v", err)
		c.Recorder.Event(webhookConfig, v1.EventTypeWarning, "invalid admission review request", err.Error())
		http.Error(w, "Decode error", http.StatusBadRequest)
		return
	}

	// TODO: This log does not get reflected in stork pods, check other logger
	arReq := admissionReview.Request
	switch arReq.Kind.Kind {
	case "StatefulSet":
		var ss appv1.StatefulSet
		if err = json.Unmarshal(arReq.Object.Raw, &ss); err != nil {
			log.Errorf("Could not unmarshal admission review object: %v", err)
			c.Recorder.Event(webhookConfig, v1.EventTypeWarning, "could not unmarshal ar object", err.Error())
		}
		isStorkResource, err = c.checkVolumeOwner(ss.Spec.Template.Spec.Volumes, ss.GetNamespace())
		if err != nil {
			c.Recorder.Event(&ss, v1.EventTypeWarning, "Could not get volume owner info for ss: %v", err.Error())
		}
		schedPath = appSchedPrefix + podSpecSchedPath
	case "Deployment":
		var deployment appv1.Deployment
		if err := json.Unmarshal(arReq.Object.Raw, &deployment); err != nil {
			log.Errorf("Could not unmarshal admission review object: %v", err)
			c.Recorder.Event(webhookConfig, v1.EventTypeWarning, "could not unmarshal ar object", err.Error())
		}
		isStorkResource, err = c.checkVolumeOwner(deployment.Spec.Template.Spec.Volumes, deployment.GetNamespace())
		if err != nil {
			c.Recorder.Event(&deployment, v1.EventTypeWarning, "Could not get volume owner info deployment: %v", err.Error())
		}
		schedPath = appSchedPrefix + podSpecSchedPath
	case "Pod":
		var pod v1.Pod
		if err := json.Unmarshal(arReq.Object.Raw, &pod); err != nil {
			log.Errorf("Could not unmarshal admission review object: %v", err)
			c.Recorder.Event(webhookConfig, v1.EventTypeWarning, "could not unmarshal ar object", err.Error())
		}
		isStorkResource, err = c.checkVolumeOwner(pod.Spec.Volumes, pod.GetNamespace())
		if err != nil {
			c.Recorder.Event(&pod, v1.EventTypeWarning, "Could not get volume owner info for pod: %v", err.Error())
		}
		schedPath = podSpecSchedPath
	}

	if !isStorkResource {
		// ignore for non driver application + resources other than depoy/ss
		admissionResponse = &v1beta1.AdmissionResponse{
			Result: &metav1.Status{
				Message: "Ignoring backends which are not supported by stork ",
			},
			Allowed: true,
		}
	} else {
		// create patch
		patch := createPatch(schedPath)
		admissionResponse = &v1beta1.AdmissionResponse{
			Result: &metav1.Status{
				Message: "Successful",
			},
			Patch:   patch,
			Allowed: true,
			PatchType: func() *v1beta1.PatchType {
				pt := v1beta1.PatchTypeJSONPatch
				return &pt
			}(),<commit_msg>use admission review request namespace to get volume info  - dont proceed if volume owner not found<commit_after>package webhookadmission

import (
	"context"
	"crypto/tls"
	"encoding/json"
	"fmt"
	"net/http"
	"os"
	"strings"
	"sync"
	"time"

	"github.com/libopenstorage/stork/drivers/volume"
	"github.com/portworx/sched-ops/k8s/core"
	log "github.com/sirupsen/logrus"
	"k8s.io/api/admission/v1beta1"
	admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
	appv1 "k8s.io/api/apps/v1"
	v1 "k8s.io/api/core/v1"
	k8serr "k8s.io/apimachinery/pkg/api/errors"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	"k8s.io/client-go/tools/record"
)

const (
	mutateWebHook            = "/mutate"
	validateWebHook          = "/validate"
	appSchedPrefix           = "/spec/template"
	podSpecSchedPath         = "/spec/schedulerName"
	storkScheduler           = "stork"
	storkAdmissionController = "stork-webhooks-cfg"
	secretName               = "servercert-secret"
	privKey                  = "privKey"
	privCert                 = "privCert"
)

// Controller for admission mutating webhook to initialise resources
// with stork as scheduler, if given resources are using driver supported
// by stork
type Controller struct {
	Recorder record.EventRecorder
	Driver   volume.Driver
	server   *http.Server
	lock     sync.Mutex
	started  bool
}

// Serve method for webhook server
func (c *Controller) serveHTTP(w http.ResponseWriter, req *http.Request) {
	if strings.Contains(req.URL.Path, mutateWebHook) {
		c.processMutateRequest(w, req)
	} else {
		http.Error(w, "Unsupported request", http.StatusNotFound)
	}
}

func (c *Controller) processMutateRequest(w http.ResponseWriter, req *http.Request) {
	var admissionResponse *v1beta1.AdmissionResponse
	var err error
	var schedPath string
	admissionReview := v1beta1.AdmissionReview{}
	isStorkResource := false

	webhookConfig := &admissionv1beta1.MutatingWebhookConfiguration{
		ObjectMeta: metav1.ObjectMeta{
			Name: storkAdmissionController,
		},
	}

	decoder := json.NewDecoder(req.Body)
	defer func() {
		if err := req.Body.Close(); err != nil {
			log.Warnf("Error closing decoder")
		}
	}()
	if err := decoder.Decode(&admissionReview); err != nil {
		log.Errorf("Error decoding admission review request: %v", err)
		c.Recorder.Event(webhookConfig, v1.EventTypeWarning, "invalid admission review request", err.Error())
		http.Error(w, "Decode error", http.StatusBadRequest)
		return
	}

	arReq := admissionReview.Request
	switch arReq.Kind.Kind {
	case "StatefulSet":
		var ss appv1.StatefulSet
		if err = json.Unmarshal(arReq.Object.Raw, &ss); err != nil {
			log.Errorf("Could not unmarshal admission review object: %v", err)
			c.Recorder.Event(webhookConfig, v1.EventTypeWarning, "could not unmarshal ar object", err.Error())
			http.Error(w, "Decode error", http.StatusBadRequest)
			return
		}
		isStorkResource, err = c.checkVolumeOwner(ss.Spec.Template.Spec.Volumes, arReq.Namespace)
		if err != nil {
			c.Recorder.Event(&ss, v1.EventTypeWarning, "Could not get volume owner info for ss: %v", err.Error())
			http.Error(w, "Could not get volume owner info", http.StatusInternalServerError)
			return
		}
		schedPath = appSchedPrefix + podSpecSchedPath
	case "Deployment":
		var deployment appv1.Deployment
		if err := json.Unmarshal(arReq.Object.Raw, &deployment); err != nil {
			log.Errorf("Could not unmarshal admission review object: %v", err)
			c.Recorder.Event(webhookConfig, v1.EventTypeWarning, "could not unmarshal ar object", err.Error())
			http.Error(w, "Decode error", http.StatusBadRequest)
			return
		}
		isStorkResource, err = c.checkVolumeOwner(deployment.Spec.Template.Spec.Volumes, arReq.Namespace)
		if err != nil {
			c.Recorder.Event(&deployment, v1.EventTypeWarning, "Could not get volume owner info deployment: %v", err.Error())
			http.Error(w, "Could not get volume owner info", http.StatusInternalServerError)
			return
		}
		schedPath = appSchedPrefix + podSpecSchedPath
	case "Pod":
		var pod v1.Pod
		if err := json.Unmarshal(arReq.Object.Raw, &pod); err != nil {
			log.Errorf("Could not unmarshal admission review object: %v", err)
			c.Recorder.Event(webhookConfig, v1.EventTypeWarning, "could not unmarshal ar object", err.Error())
			http.Error(w, "Decode error", http.StatusBadRequest)
			return
		}
		isStorkResource, err = c.checkVolumeOwner(pod.Spec.Volumes, arReq.Namespace)
		if err != nil {
			c.Recorder.Event(&pod, v1.EventTypeWarning, "Could not get volume owner info for pod: %v", err.Error())
			http.Error(w, "Could not get volume owner info", http.StatusInternalServerError)
			return
		}
		schedPath = podSpecSchedPath
	}

	if !isStorkResource {
		// ignore for non driver application + resources other than depoy/ss
		admissionResponse = &v1beta1.AdmissionResponse{
			Result: &metav1.Status{
				Message: "Ignoring backends which are not supported by stork ",
			},
			Allowed: true,
		}
	} else {
		// create patch
		patch := createPatch(schedPath)
		admissionResponse = &v1beta1.AdmissionResponse{
			Result: &metav1.Status{
				Message: "Successful",
			},
			Patch:   patch,
			Allowed: true,
			PatchType: func() *v1beta1.PatchType {
				pt := v1beta1.PatchTypeJSONPatch
				return &pt
			}(),
{'fraction_non_alphanumeric': 0.11882483147197907, 'fraction_numerical': 0.009658919408391186, 'mean_word_length': 3.960079840319361, 'pattern_counts': {'":': 6, '<': 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': '4868939', 'n_tokens_mistral': 3595, 'n_tokens_neox': 3343, 'n_words': 794}