github.com/zppinho/prow@v0.0.0-20240510014325-1738badeb017/cmd/webhook-server/mutation.go (about)

     1  /*
     2  Copyright 2022 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package main
    18  
    19  import (
    20  	"encoding/json"
    21  	"fmt"
    22  	"io"
    23  	"net/http"
    24  
    25  	"github.com/sirupsen/logrus"
    26  	"gomodules.xyz/jsonpatch/v2"
    27  	"k8s.io/api/admission/v1beta1"
    28  	apiv1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/types"
    30  	"sigs.k8s.io/prow/pkg/apis/prowjobs/v1"
    31  	"sigs.k8s.io/prow/pkg/config"
    32  )
    33  
    34  func (wa *webhookAgent) serveMutate(w http.ResponseWriter, r *http.Request) {
    35  	body, err := io.ReadAll(r.Body)
    36  	if err != nil {
    37  		logrus.WithError(err).Info("unable to read request")
    38  		http.Error(w, fmt.Sprintf("bad request %v", err), http.StatusBadRequest)
    39  		return
    40  	}
    41  	admissionReview := &v1beta1.AdmissionReview{}
    42  	err = json.Unmarshal(body, admissionReview)
    43  	if err != nil {
    44  		logrus.WithError(err).Info("unable to unmarshal admission review request")
    45  		http.Error(w, fmt.Sprintf("unable to unmarshal admission review request %v", err), http.StatusBadRequest)
    46  		return
    47  	}
    48  	admissionRequest := admissionReview.Request
    49  	var prowJob v1.ProwJob
    50  	err = json.Unmarshal(admissionRequest.Object.Raw, &prowJob)
    51  	if err != nil {
    52  		logrus.WithError(err).Info("unable to prowjob from request")
    53  		http.Error(w, fmt.Sprintf("unable to unmarshal prowjob %v", err), http.StatusBadRequest)
    54  		return
    55  	}
    56  	var mutatedProwJobPatch []byte
    57  	if admissionRequest.Operation == "CREATE" {
    58  		mutatedProwJobPatch, err = generateMutatingPatch(&prowJob, wa.plank)
    59  		if err != nil {
    60  			logrus.WithError(err).Info("unable to return mutated prowjob patch")
    61  			http.Error(w, fmt.Sprintf("unable to return mutated prowjob patch %v", err), http.StatusInternalServerError)
    62  			return
    63  		}
    64  	}
    65  	admissionReview.Response = createMutatingAdmissionResponse(admissionRequest.UID, mutatedProwJobPatch)
    66  	resp, err := json.Marshal(admissionReview)
    67  	if err != nil {
    68  		logrus.WithError(err).Info("unable to marshal response")
    69  		http.Error(w, fmt.Sprintf("unable to marshal mutated prowjob patch %v", err), http.StatusInternalServerError)
    70  		return
    71  	}
    72  	if _, err := w.Write(resp); err != nil {
    73  		logrus.WithError(err).Info("unable to write response")
    74  		http.Error(w, fmt.Sprintf("unable to write response: %v", err), http.StatusInternalServerError)
    75  		return
    76  	}
    77  }
    78  
    79  func createMutatingAdmissionResponse(uid types.UID, patch []byte) *v1beta1.AdmissionResponse {
    80  	return &v1beta1.AdmissionResponse{
    81  		UID:     uid,
    82  		Allowed: true,
    83  		Result: &apiv1.Status{
    84  			Message: accepted,
    85  		},
    86  		Patch: patch,
    87  		PatchType: func() *v1beta1.PatchType {
    88  			pt := v1beta1.PatchTypeJSONPatch
    89  			return &pt
    90  		}(),
    91  	}
    92  }
    93  
    94  func generateMutatingPatch(prowJob *v1.ProwJob, plank config.Plank) ([]byte, error) {
    95  	var defDecorationConfig *v1.DecorationConfig
    96  	var patchBytes []byte
    97  	if prowJob.Spec.Type == v1.PeriodicJob {
    98  		var repo string
    99  		if len(prowJob.Spec.ExtraRefs) > 0 {
   100  			repo = fmt.Sprintf("%s/%s", prowJob.Spec.ExtraRefs[0].Org, prowJob.Spec.ExtraRefs[0].Repo)
   101  		}
   102  		defDecorationConfig = plank.GuessDefaultDecorationConfigWithJobDC(repo, prowJob.Spec.Cluster, prowJob.Spec.DecorationConfig)
   103  	} else {
   104  		defDecorationConfig = plank.GuessDefaultDecorationConfig(prowJob.Spec.Refs.Repo, prowJob.Spec.Cluster)
   105  	}
   106  	prowJobCopy := prowJob.DeepCopy()
   107  	prowJobCopy.Spec.DecorationConfig = prowJobCopy.Spec.DecorationConfig.ApplyDefault(defDecorationConfig)
   108  	originalProwJobJSON, err := json.Marshal(prowJob)
   109  	if err != nil {
   110  		return nil, fmt.Errorf("unable to marshal prowjob %v", err)
   111  	}
   112  	mutatedProwJobJSON, err := json.Marshal(prowJobCopy)
   113  	if err != nil {
   114  		return nil, fmt.Errorf("unable to marshal prowjob %v", err)
   115  	}
   116  	patch, err := jsonpatch.CreatePatch(originalProwJobJSON, mutatedProwJobJSON)
   117  	if err != nil {
   118  		return nil, fmt.Errorf("unable to create json patch")
   119  	}
   120  	patchBytes, err = json.Marshal(patch)
   121  	if err != nil {
   122  		return nil, fmt.Errorf("unable to marshal patch")
   123  	}
   124  
   125  	return patchBytes, nil
   126  }