Skip to content

Commit 091a5dc

Browse files
authored
Merge pull request kubernetes#81314 from jpbetz/crd-test-image
Upgrade ConversionReview e2e test image to also support v1
2 parents f12a40d + 3f519b0 commit 091a5dc

File tree

5 files changed

+203
-92
lines changed

5 files changed

+203
-92
lines changed

test/images/agnhost/crd-conversion-webhook/converter/BUILD

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,13 @@ go_library(
99
importpath = "k8s.io/kubernetes/test/images/agnhost/crd-conversion-webhook/converter",
1010
visibility = ["//visibility:public"],
1111
deps = [
12+
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
1213
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
1314
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
1415
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
1516
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
1617
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library",
18+
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
1719
"//vendor/github.com/munnerz/goautoneg:go_default_library",
1820
"//vendor/k8s.io/klog:go_default_library",
1921
],
@@ -24,7 +26,7 @@ go_test(
2426
srcs = ["converter_test.go"],
2527
embed = [":go_default_library"],
2628
deps = [
27-
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
29+
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
2830
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
2931
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library",
3032
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",

test/images/agnhost/crd-conversion-webhook/converter/converter_test.go

Lines changed: 71 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -17,21 +17,47 @@ limitations under the License.
1717
package converter
1818

1919
import (
20+
"bytes"
21+
"fmt"
2022
"net/http"
2123
"net/http/httptest"
2224
"strings"
2325
"testing"
2426

25-
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
27+
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2628
"k8s.io/apimachinery/pkg/apis/meta/v1"
2729
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
2830
"k8s.io/apimachinery/pkg/runtime"
2931
"k8s.io/apimachinery/pkg/runtime/serializer/json"
3032
)
3133

32-
func TestConverter(t *testing.T) {
33-
sampleObj := `kind: ConversionReview
34-
apiVersion: apiextensions.k8s.io/v1beta1
34+
func TestConverterYAML(t *testing.T) {
35+
cases := []struct {
36+
apiVersion string
37+
contentType string
38+
expected400Err string
39+
}{
40+
{
41+
apiVersion: "apiextensions.k8s.io/v1beta1",
42+
contentType: "application/json",
43+
expected400Err: "json parse error",
44+
},
45+
{
46+
apiVersion: "apiextensions.k8s.io/v1beta1",
47+
contentType: "application/yaml",
48+
},
49+
{
50+
apiVersion: "apiextensions.k8s.io/v1",
51+
contentType: "application/json",
52+
expected400Err: "json parse error",
53+
},
54+
{
55+
apiVersion: "apiextensions.k8s.io/v1",
56+
contentType: "application/yaml",
57+
},
58+
}
59+
sampleObjTemplate := `kind: ConversionReview
60+
apiVersion: %s
3561
request:
3662
uid: 0000-0000-0000-0000
3763
desiredAPIVersion: stable.example.com/v2
@@ -45,53 +71,47 @@ request:
4571
image: my-awesome-cron-image
4672
hostPort: "localhost:7070"
4773
`
48-
// First try json, it should fail as the data is taml
49-
response := httptest.NewRecorder()
50-
request, err := http.NewRequest("POST", "/convert", strings.NewReader(sampleObj))
51-
if err != nil {
52-
t.Fatal(err)
53-
}
54-
request.Header.Add("Content-Type", "application/json")
55-
ServeExampleConvert(response, request)
56-
convertReview := v1beta1.ConversionReview{}
57-
scheme := runtime.NewScheme()
58-
jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false)
59-
if _, _, err := jsonSerializer.Decode(response.Body.Bytes(), nil, &convertReview); err != nil {
60-
t.Fatal(err)
61-
}
62-
if convertReview.Response.Result.Status != v1.StatusFailure {
63-
t.Fatalf("expected the operation to fail when yaml is provided with json header")
64-
} else if !strings.Contains(convertReview.Response.Result.Message, "json parse error") {
65-
t.Fatalf("expected to fail on json parser, but it failed with: %v", convertReview.Response.Result.Message)
66-
}
74+
for _, tc := range cases {
75+
t.Run(tc.apiVersion+" "+tc.contentType, func(t *testing.T) {
76+
sampleObj := fmt.Sprintf(sampleObjTemplate, tc.apiVersion)
77+
// First try json, it should fail as the data is taml
78+
response := httptest.NewRecorder()
79+
request, err := http.NewRequest("POST", "/convert", strings.NewReader(sampleObj))
80+
if err != nil {
81+
t.Fatal(err)
82+
}
83+
request.Header.Add("Content-Type", tc.contentType)
84+
ServeExampleConvert(response, request)
85+
convertReview := apiextensionsv1.ConversionReview{}
86+
scheme := runtime.NewScheme()
87+
if len(tc.expected400Err) > 0 {
88+
body := response.Body.Bytes()
89+
if !bytes.Contains(body, []byte(tc.expected400Err)) {
90+
t.Fatalf("expected to fail on '%s', but it failed with: %s", tc.expected400Err, string(body))
91+
}
92+
return
93+
}
6794

68-
// Now try yaml, and it should successfully convert
69-
response = httptest.NewRecorder()
70-
request, err = http.NewRequest("POST", "/convert", strings.NewReader(sampleObj))
71-
if err != nil {
72-
t.Fatal(err)
73-
}
74-
request.Header.Add("Content-Type", "application/yaml")
75-
ServeExampleConvert(response, request)
76-
convertReview = v1beta1.ConversionReview{}
77-
yamlSerializer := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme, scheme)
78-
if _, _, err := yamlSerializer.Decode(response.Body.Bytes(), nil, &convertReview); err != nil {
79-
t.Fatalf("cannot decode data: \n %v\n Error: %v", response.Body, err)
80-
}
81-
if convertReview.Response.Result.Status != v1.StatusSuccess {
82-
t.Fatalf("cr conversion failed: %v", convertReview.Response)
83-
}
84-
convertedObj := unstructured.Unstructured{}
85-
if _, _, err := yamlSerializer.Decode(convertReview.Response.ConvertedObjects[0].Raw, nil, &convertedObj); err != nil {
86-
t.Fatal(err)
87-
}
88-
if e, a := "stable.example.com/v2", convertedObj.GetAPIVersion(); e != a {
89-
t.Errorf("expected= %v, actual= %v", e, a)
90-
}
91-
if e, a := "localhost", convertedObj.Object["host"]; e != a {
92-
t.Errorf("expected= %v, actual= %v", e, a)
93-
}
94-
if e, a := "7070", convertedObj.Object["port"]; e != a {
95-
t.Errorf("expected= %v, actual= %v", e, a)
95+
yamlSerializer := json.NewYAMLSerializer(json.DefaultMetaFactory, scheme, scheme)
96+
if _, _, err := yamlSerializer.Decode(response.Body.Bytes(), nil, &convertReview); err != nil {
97+
t.Fatalf("cannot decode data: \n %v\n Error: %v", response.Body, err)
98+
}
99+
if convertReview.Response.Result.Status != v1.StatusSuccess {
100+
t.Fatalf("cr conversion failed: %v", convertReview.Response)
101+
}
102+
convertedObj := unstructured.Unstructured{}
103+
if _, _, err := yamlSerializer.Decode(convertReview.Response.ConvertedObjects[0].Raw, nil, &convertedObj); err != nil {
104+
t.Fatal(err)
105+
}
106+
if e, a := "stable.example.com/v2", convertedObj.GetAPIVersion(); e != a {
107+
t.Errorf("expected= %v, actual= %v", e, a)
108+
}
109+
if e, a := "localhost", convertedObj.Object["host"]; e != a {
110+
t.Errorf("expected= %v, actual= %v", e, a)
111+
}
112+
if e, a := "7070", convertedObj.Object["port"]; e != a {
113+
t.Errorf("expected= %v, actual= %v", e, a)
114+
}
115+
})
96116
}
97117
}

test/images/agnhost/crd-conversion-webhook/converter/framework.go

Lines changed: 96 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -26,29 +26,19 @@ import (
2626

2727
"k8s.io/klog"
2828

29+
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
2930
"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
3031
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
3132
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
3233
"k8s.io/apimachinery/pkg/runtime"
3334
"k8s.io/apimachinery/pkg/runtime/serializer/json"
35+
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
3436
)
3537

3638
// convertFunc is the user defined function for any conversion. The code in this file is a
3739
// template that can be use for any CR conversion given this function.
3840
type convertFunc func(Object *unstructured.Unstructured, version string) (*unstructured.Unstructured, metav1.Status)
3941

40-
// conversionResponseFailureWithMessagef is a helper function to create an AdmissionResponse
41-
// with a formatted embedded error message.
42-
func conversionResponseFailureWithMessagef(msg string, params ...interface{}) *v1beta1.ConversionResponse {
43-
return &v1beta1.ConversionResponse{
44-
Result: metav1.Status{
45-
Message: fmt.Sprintf(msg, params...),
46-
Status: metav1.StatusFailure,
47-
},
48-
}
49-
50-
}
51-
5242
func statusErrorWithMessage(msg string, params ...interface{}) metav1.Status {
5343
return metav1.Status{
5444
Message: fmt.Sprintf(msg, params...),
@@ -62,15 +52,20 @@ func statusSucceed() metav1.Status {
6252
}
6353
}
6454

65-
// doConversion converts the requested object given the conversion function and returns a conversion response.
66-
// failures will be reported as Reason in the conversion response.
67-
func doConversion(convertRequest *v1beta1.ConversionRequest, convert convertFunc) *v1beta1.ConversionResponse {
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 {
6858
var convertedObjects []runtime.RawExtension
6959
for _, obj := range convertRequest.Objects {
7060
cr := unstructured.Unstructured{}
7161
if err := cr.UnmarshalJSON(obj.Raw); err != nil {
7262
klog.Error(err)
73-
return conversionResponseFailureWithMessagef("failed to unmarshall object (%v) with error: %v", string(obj.Raw), 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+
}
7469
}
7570
convertedCR, status := convert(&cr, convertRequest.DesiredAPIVersion)
7671
if status.Status != metav1.StatusSuccess {
@@ -88,6 +83,37 @@ func doConversion(convertRequest *v1beta1.ConversionRequest, convert convertFunc
8883
}
8984
}
9085

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+
91117
func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
92118
var body []byte
93119
if r.Body != nil {
@@ -106,18 +132,52 @@ func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
106132
}
107133

108134
klog.V(2).Infof("handling request: %v", body)
109-
convertReview := v1beta1.ConversionReview{}
110-
if _, _, err := serializer.Decode(body, nil, &convertReview); err != nil {
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)
111138
klog.Error(err)
112-
convertReview.Response = conversionResponseFailureWithMessagef("failed to deserialize body (%v) with error %v", string(body), err)
113-
} else {
114-
convertReview.Response = doConversion(convertReview.Request, convert)
115-
convertReview.Response.UID = convertReview.Request.UID
139+
http.Error(w, msg, http.StatusBadRequest)
140+
return
116141
}
117-
klog.V(2).Info(fmt.Sprintf("sending response: %v", convertReview.Response))
118142

119-
// reset the request, it is not needed in a response.
120-
convertReview.Request = &v1beta1.ConversionRequest{}
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+
}
121181

122182
accept := r.Header.Get("Accept")
123183
outSerializer := getOutputSerializer(accept)
@@ -127,7 +187,7 @@ func serve(w http.ResponseWriter, r *http.Request, convert convertFunc) {
127187
http.Error(w, msg, http.StatusBadRequest)
128188
return
129189
}
130-
err := outSerializer.Encode(&convertReview, w)
190+
err = outSerializer.Encode(responseObj, w)
131191
if err != nil {
132192
klog.Error(err)
133193
http.Error(w, err.Error(), http.StatusInternalServerError)
@@ -145,6 +205,16 @@ type mediaType struct {
145205
}
146206

147207
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+
148218
var serializers = map[mediaType]runtime.Serializer{
149219
{"application", "json"}: json.NewSerializer(json.DefaultMetaFactory, scheme, scheme, false),
150220
{"application", "yaml"}: json.NewYAMLSerializer(json.DefaultMetaFactory, scheme, scheme),

test/images/agnhost/webhook/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ go_library(
2323
"//staging/src/k8s.io/api/admissionregistration/v1:go_default_library",
2424
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
2525
"//staging/src/k8s.io/api/core/v1:go_default_library",
26+
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:go_default_library",
2627
"//staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1:go_default_library",
2728
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
2829
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",

0 commit comments

Comments
 (0)