...

Source file src/k8s.io/kubernetes/test/images/agnhost/crd-conversion-webhook/converter/framework.go

Documentation: k8s.io/kubernetes/test/images/agnhost/crd-conversion-webhook/converter

     1  /*
     2  Copyright 2018 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 converter
    18  
    19  import (
    20  	"fmt"
    21  	"io"
    22  	"net/http"
    23  	"strings"
    24  
    25  	"github.com/munnerz/goautoneg"
    26  
    27  	"k8s.io/klog/v2"
    28  
    29  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
    30  	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
    31  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    32  	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
    33  	"k8s.io/apimachinery/pkg/runtime"
    34  	"k8s.io/apimachinery/pkg/runtime/serializer/json"
    35  	utilruntime "k8s.io/apimachinery/pkg/util/runtime"
    36  )
    37  
    38  // convertFunc is the user defined function for any conversion. The code in this file is a
    39  // template that can be use for any CR conversion given this function.
    40  type convertFunc func(Object *unstructured.Unstructured, version string) (*unstructured.Unstructured, metav1.Status)
    41  
    42  func statusErrorWithMessage(msg string, params ...interface{}) metav1.Status {
    43  	return metav1.Status{
    44  		Message: fmt.Sprintf(msg, params...),
    45  		Status:  metav1.StatusFailure,
    46  	}
    47  }
    48  
    49  func statusSucceed() metav1.Status {
    50  	return metav1.Status{
    51  		Status: metav1.StatusSuccess,
    52  	}
    53  }
    54  
    55  // doConversionV1beta1 converts the requested objects in the v1beta1 ConversionRequest using the given conversion function and
    56  // returns a conversion response. Failures are reported with the Reason in the conversion response.
    57  func doConversionV1beta1(convertRequest *v1beta1.ConversionRequest, convert convertFunc) *v1beta1.ConversionResponse {
    58  	var convertedObjects []runtime.RawExtension
    59  	for _, obj := range convertRequest.Objects {
    60  		cr := unstructured.Unstructured{}
    61  		if err := cr.UnmarshalJSON(obj.Raw); err != nil {
    62  			klog.Error(err)
    63  			return &v1beta1.ConversionResponse{
    64  				Result: metav1.Status{
    65  					Message: fmt.Sprintf("failed to unmarshall object (%v) with error: %v", string(obj.Raw), err),
    66  					Status:  metav1.StatusFailure,
    67  				},
    68  			}
    69  		}
    70  		convertedCR, status := convert(&cr, convertRequest.DesiredAPIVersion)
    71  		if status.Status != metav1.StatusSuccess {
    72  			klog.Error(status.String())
    73  			return &v1beta1.ConversionResponse{
    74  				Result: status,
    75  			}
    76  		}
    77  		convertedCR.SetAPIVersion(convertRequest.DesiredAPIVersion)
    78  		convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedCR})
    79  	}
    80  	return &v1beta1.ConversionResponse{
    81  		ConvertedObjects: convertedObjects,
    82  		Result:           statusSucceed(),
    83  	}
    84  }
    85  
    86  // doConversionV1 converts the requested objects in the v1 ConversionRequest using the given conversion function and
    87  // returns a conversion response. Failures are reported with the Reason in the conversion response.
    88  func doConversionV1(convertRequest *v1.ConversionRequest, convert convertFunc) *v1.ConversionResponse {
    89  	var convertedObjects []runtime.RawExtension
    90  	for _, obj := range convertRequest.Objects {
    91  		cr := unstructured.Unstructured{}
    92  		if err := cr.UnmarshalJSON(obj.Raw); err != nil {
    93  			klog.Error(err)
    94  			return &v1.ConversionResponse{
    95  				Result: metav1.Status{
    96  					Message: fmt.Sprintf("failed to unmarshall object (%v) with error: %v", string(obj.Raw), err),
    97  					Status:  metav1.StatusFailure,
    98  				},
    99  			}
   100  		}
   101  		convertedCR, status := convert(&cr, convertRequest.DesiredAPIVersion)
   102  		if status.Status != metav1.StatusSuccess {
   103  			klog.Error(status.String())
   104  			return &v1.ConversionResponse{
   105  				Result: status,
   106  			}
   107  		}
   108  		convertedCR.SetAPIVersion(convertRequest.DesiredAPIVersion)
   109  		convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedCR})
   110  	}
   111  	return &v1.ConversionResponse{
   112  		ConvertedObjects: convertedObjects,
   113  		Result:           statusSucceed(),
   114  	}
   115  }
   116  
   117  func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
   118  	var body []byte
   119  	if r.Body != nil {
   120  		if data, err := io.ReadAll(r.Body); err == nil {
   121  			body = data
   122  		}
   123  	}
   124  
   125  	contentType := r.Header.Get("Content-Type")
   126  	serializer := getInputSerializer(contentType)
   127  	if serializer == nil {
   128  		msg := fmt.Sprintf("invalid Content-Type header `%s`", contentType)
   129  		klog.Errorf(msg)
   130  		http.Error(w, msg, http.StatusBadRequest)
   131  		return
   132  	}
   133  
   134  	klog.V(2).Infof("handling request: %v", body)
   135  	obj, gvk, err := serializer.Decode(body, nil, nil)
   136  	if err != nil {
   137  		msg := fmt.Sprintf("failed to deserialize body (%v) with error %v", string(body), err)
   138  		klog.Error(err)
   139  		http.Error(w, msg, http.StatusBadRequest)
   140  		return
   141  	}
   142  
   143  	var responseObj runtime.Object
   144  	switch *gvk {
   145  	case v1beta1.SchemeGroupVersion.WithKind("ConversionReview"):
   146  		convertReview, ok := obj.(*v1beta1.ConversionReview)
   147  		if !ok {
   148  			msg := fmt.Sprintf("Expected v1beta1.ConversionReview but got: %T", obj)
   149  			klog.Errorf(msg)
   150  			http.Error(w, msg, http.StatusBadRequest)
   151  			return
   152  		}
   153  		convertReview.Response = doConversionV1beta1(convertReview.Request, convert)
   154  		convertReview.Response.UID = convertReview.Request.UID
   155  		klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))
   156  
   157  		// reset the request, it is not needed in a response.
   158  		convertReview.Request = &v1beta1.ConversionRequest{}
   159  		responseObj = convertReview
   160  	case v1.SchemeGroupVersion.WithKind("ConversionReview"):
   161  		convertReview, ok := obj.(*v1.ConversionReview)
   162  		if !ok {
   163  			msg := fmt.Sprintf("Expected v1.ConversionReview but got: %T", obj)
   164  			klog.Errorf(msg)
   165  			http.Error(w, msg, http.StatusBadRequest)
   166  			return
   167  		}
   168  		convertReview.Response = doConversionV1(convertReview.Request, convert)
   169  		convertReview.Response.UID = convertReview.Request.UID
   170  		klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))
   171  
   172  		// reset the request, it is not needed in a response.
   173  		convertReview.Request = &v1.ConversionRequest{}
   174  		responseObj = convertReview
   175  	default:
   176  		msg := fmt.Sprintf("Unsupported group version kind: %v", gvk)
   177  		klog.Error(err)
   178  		http.Error(w, msg, http.StatusBadRequest)
   179  		return
   180  	}
   181  
   182  	accept := r.Header.Get("Accept")
   183  	outSerializer := getOutputSerializer(accept)
   184  	if outSerializer == nil {
   185  		msg := fmt.Sprintf("invalid accept header `%s`", accept)
   186  		klog.Errorf(msg)
   187  		http.Error(w, msg, http.StatusBadRequest)
   188  		return
   189  	}
   190  	err = outSerializer.Encode(responseObj, w)
   191  	if err != nil {
   192  		klog.Error(err)
   193  		http.Error(w, err.Error(), http.StatusInternalServerError)
   194  		return
   195  	}
   196  }
   197  
   198  // ServeExampleConvert servers endpoint for the example converter defined as convertExampleCRD function.
   199  func ServeExampleConvert(w http.ResponseWriter, r *http.Request) {
   200  	serve(w, r, convertExampleCRD)
   201  }
   202  
   203  type mediaType struct {
   204  	Type, SubType string
   205  }
   206  
   207  var scheme = runtime.NewScheme()
   208  
   209  func init() {
   210  	addToScheme(scheme)
   211  }
   212  
   213  func addToScheme(scheme *runtime.Scheme) {
   214  	utilruntime.Must(v1.AddToScheme(scheme))
   215  	utilruntime.Must(v1beta1.AddToScheme(scheme))
   216  }
   217  
   218  var serializers = map[mediaType]runtime.Serializer{
   219  	{"application", "json"}: json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, json.SerializerOptions{Pretty: false}),
   220  	{"application", "yaml"}: json.NewSerializerWithOptions(json.DefaultMetaFactory, scheme, scheme, json.SerializerOptions{Yaml: true}),
   221  }
   222  
   223  func getInputSerializer(contentType string) runtime.Serializer {
   224  	parts := strings.SplitN(contentType, "/", 2)
   225  	if len(parts) != 2 {
   226  		return nil
   227  	}
   228  	return serializers[mediaType{parts[0], parts[1]}]
   229  }
   230  
   231  func getOutputSerializer(accept string) runtime.Serializer {
   232  	if len(accept) == 0 {
   233  		return serializers[mediaType{"application", "json"}]
   234  	}
   235  
   236  	clauses := goautoneg.ParseAccept(accept)
   237  	for _, clause := range clauses {
   238  		for k, v := range serializers {
   239  			switch {
   240  			case clause.Type == k.Type && clause.SubType == k.SubType,
   241  				clause.Type == k.Type && clause.SubType == "*",
   242  				clause.Type == "*" && clause.SubType == "*":
   243  				return v
   244  			}
   245  		}
   246  	}
   247  
   248  	return nil
   249  }
   250  

View as plain text