Move to github.com/munnerz/goautoneg as bitbucket is flaky! Change-Id: Iaa6e964ef0d6f308eea59bcc6f365ecd7dbf0784
180 lines
5.6 KiB
Go
180 lines
5.6 KiB
Go
/*
|
|
Copyright 2018 The Kubernetes Authors.
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
you may not use this file except in compliance with the License.
|
|
You may obtain a copy of the License at
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
See the License for the specific language governing permissions and
|
|
limitations under the License.
|
|
*/
|
|
|
|
package converter
|
|
|
|
import (
|
|
"fmt"
|
|
"io/ioutil"
|
|
"net/http"
|
|
"strings"
|
|
|
|
"github.com/munnerz/goautoneg"
|
|
|
|
"k8s.io/klog"
|
|
|
|
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
|
"k8s.io/apimachinery/pkg/runtime"
|
|
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
|
)
|
|
|
|
// convertFunc is the user defined function for any conversion. The code in this file is a
|
|
// template that can be use for any CR conversion given this function.
|
|
type convertFunc func(Object *unstructured.Unstructured, version string) (*unstructured.Unstructured, metav1.Status)
|
|
|
|
// conversionResponseFailureWithMessagef is a helper function to create an AdmissionResponse
|
|
// with a formatted embedded error message.
|
|
func conversionResponseFailureWithMessagef(msg string, params ...interface{}) *v1beta1.ConversionResponse {
|
|
return &v1beta1.ConversionResponse{
|
|
Result: metav1.Status{
|
|
Message: fmt.Sprintf(msg, params...),
|
|
Status: metav1.StatusFailure,
|
|
},
|
|
}
|
|
|
|
}
|
|
|
|
func statusErrorWithMessage(msg string, params ...interface{}) metav1.Status {
|
|
return metav1.Status{
|
|
Message: fmt.Sprintf(msg, params...),
|
|
Status: metav1.StatusFailure,
|
|
}
|
|
}
|
|
|
|
func statusSucceed() metav1.Status {
|
|
return metav1.Status{
|
|
Status: metav1.StatusSuccess,
|
|
}
|
|
}
|
|
|
|
// doConversion converts the requested object given the conversion function and returns a conversion response.
|
|
// failures will be reported as Reason in the conversion response.
|
|
func doConversion(convertRequest *v1beta1.ConversionRequest, convert convertFunc) *v1beta1.ConversionResponse {
|
|
var convertedObjects []runtime.RawExtension
|
|
for _, obj := range convertRequest.Objects {
|
|
cr := unstructured.Unstructured{}
|
|
if err := cr.UnmarshalJSON(obj.Raw); err != nil {
|
|
klog.Error(err)
|
|
return conversionResponseFailureWithMessagef("failed to unmarshall object (%v) with error: %v", string(obj.Raw), err)
|
|
}
|
|
convertedCR, status := convert(&cr, convertRequest.DesiredAPIVersion)
|
|
if status.Status != metav1.StatusSuccess {
|
|
klog.Error(status.String())
|
|
return &v1beta1.ConversionResponse{
|
|
Result: status,
|
|
}
|
|
}
|
|
convertedCR.SetAPIVersion(convertRequest.DesiredAPIVersion)
|
|
convertedObjects = append(convertedObjects, runtime.RawExtension{Object: convertedCR})
|
|
}
|
|
return &v1beta1.ConversionResponse{
|
|
ConvertedObjects: convertedObjects,
|
|
Result: statusSucceed(),
|
|
}
|
|
}
|
|
|
|
func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
|
|
var body []byte
|
|
if r.Body != nil {
|
|
if data, err := ioutil.ReadAll(r.Body); err == nil {
|
|
body = data
|
|
}
|
|
}
|
|
|
|
contentType := r.Header.Get("Content-Type")
|
|
serializer := getInputSerializer(contentType)
|
|
if serializer == nil {
|
|
msg := fmt.Sprintf("invalid Content-Type header `%s`", contentType)
|
|
klog.Errorf(msg)
|
|
http.Error(w, msg, http.StatusBadRequest)
|
|
return
|
|
}
|
|
|
|
klog.V(2).Infof("handling request: %v", body)
|
|
convertReview := v1beta1.ConversionReview{}
|
|
if _, _, err := serializer.Decode(body, nil, &convertReview); err != nil {
|
|
klog.Error(err)
|
|
convertReview.Response = conversionResponseFailureWithMessagef("failed to deserialize body (%v) with error %v", string(body), err)
|
|
} else {
|
|
convertReview.Response = doConversion(convertReview.Request, convert)
|
|
convertReview.Response.UID = convertReview.Request.UID
|
|
}
|
|
klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))
|
|
|
|
// reset the request, it is not needed in a response.
|
|
convertReview.Request = &v1beta1.ConversionRequest{}
|
|
|
|
accept := r.Header.Get("Accept")
|
|
outSerializer := getOutputSerializer(accept)
|
|
if outSerializer == nil {
|
|
msg := fmt.Sprintf("invalid accept header `%s`", accept)
|
|
klog.Errorf(msg)
|
|
http.Error(w, msg, http.StatusBadRequest)
|
|
return
|
|
}
|
|
err := outSerializer.Encode(&convertReview, w)
|
|
if err != nil {
|
|
klog.Error(err)
|
|
http.Error(w, err.Error(), http.StatusInternalServerError)
|
|
return
|
|
}
|
|
}
|
|
|
|
// ServeExampleConvert servers endpoint for the example converter defined as convertExampleCRD function.
|
|
func ServeExampleConvert(w http.ResponseWriter, r *http.Request) {
|
|
serve(w, r, convertExampleCRD)
|
|
}
|
|
|
|
type mediaType struct {
|
|
Type, SubType string
|
|
}
|
|
|
|
var scheme = runtime.NewScheme()
|
|
var serializers = map[mediaType]runtime.Serializer{
|
|
{"application", "json"}: json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false),
|
|
{"application", "yaml"}: json.NewYAMLSerializer(json.DefaultMetaFactory, scheme, scheme),
|
|
}
|
|
|
|
func getInputSerializer(contentType string) runtime.Serializer {
|
|
parts := strings.SplitN(contentType, "/", 2)
|
|
if len(parts) != 2 {
|
|
return nil
|
|
}
|
|
return serializers[mediaType{parts[0], parts[1]}]
|
|
}
|
|
|
|
func getOutputSerializer(accept string) runtime.Serializer {
|
|
if len(accept) == 0 {
|
|
return serializers[mediaType{"application", "json"}]
|
|
}
|
|
|
|
clauses := goautoneg.ParseAccept(accept)
|
|
for _, clause := range clauses {
|
|
for k, v := range serializers {
|
|
switch {
|
|
case clause.Type == k.Type && clause.SubType == k.SubType,
|
|
clause.Type == k.Type && clause.SubType == "*",
|
|
clause.Type == "*" && clause.SubType == "*":
|
|
return v
|
|
}
|
|
}
|
|
}
|
|
|
|
return nil
|
|
}
|