Skip to content

Commit ab11fc3

Browse files
author
Mengqi Yu
committed
add tests for webhook/admission pkg
1 parent ab29e82 commit ab11fc3

File tree

9 files changed

+798
-16
lines changed

9 files changed

+798
-16
lines changed
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
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 admission
18+
19+
import (
20+
"testing"
21+
22+
. "github.com/onsi/ginkgo"
23+
. "github.com/onsi/gomega"
24+
25+
"sigs.k8s.io/controller-runtime/pkg/envtest"
26+
logf "sigs.k8s.io/controller-runtime/pkg/runtime/log"
27+
)
28+
29+
func TestSource(t *testing.T) {
30+
RegisterFailHandler(Fail)
31+
RunSpecsWithDefaultAndCustomReporters(t, "application Suite", []Reporter{envtest.NewlineReporter{}})
32+
}
33+
34+
var _ = BeforeSuite(func(done Done) {
35+
logf.SetLogger(logf.ZapLoggerTo(GinkgoWriter, true))
36+
37+
close(done)
38+
}, 60)

pkg/webhook/admission/decode_test.go

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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 admission
18+
19+
import (
20+
. "github.com/onsi/ginkgo"
21+
. "github.com/onsi/gomega"
22+
23+
admissionv1beta1 "k8s.io/api/admission/v1beta1"
24+
corev1 "k8s.io/api/core/v1"
25+
"k8s.io/apimachinery/pkg/runtime"
26+
"k8s.io/client-go/kubernetes/scheme"
27+
)
28+
29+
var _ = Describe("admission webhook decoder", func() {
30+
Describe("NewDecoder", func() {
31+
It("should return a decoder without an error", func() {
32+
decoder, err := NewDecoder(scheme.Scheme)
33+
Expect(err).NotTo(HaveOccurred())
34+
Expect(decoder).NotTo(BeNil())
35+
})
36+
})
37+
38+
Describe("Decode", func() {
39+
var decoder Decoder
40+
req := Request{
41+
AdmissionRequest: &admissionv1beta1.AdmissionRequest{
42+
Object: runtime.RawExtension{
43+
Raw: []byte(`{
44+
"apiVersion": "v1",
45+
"kind": "Pod",
46+
"metadata": {
47+
"name": "foo",
48+
"namespace": "default"
49+
},
50+
"spec": {
51+
"containers": [
52+
{
53+
"image": "bar",
54+
"name": "bar"
55+
}
56+
]
57+
}
58+
}`),
59+
},
60+
},
61+
}
62+
BeforeEach(func(done Done) {
63+
var err error
64+
decoder, err = NewDecoder(scheme.Scheme)
65+
Expect(err).NotTo(HaveOccurred())
66+
Expect(decoder).NotTo(BeNil())
67+
close(done)
68+
})
69+
70+
It("should be able to decode", func() {
71+
err := decoder.Decode(req, &corev1.Pod{})
72+
Expect(err).NotTo(HaveOccurred())
73+
})
74+
75+
It("should return an error if the GVK mismatch", func() {
76+
err := decoder.Decode(req, &corev1.Node{})
77+
Expect(err).To(HaveOccurred())
78+
Expect(err.Error()).Should(ContainSubstring("unable to decode"))
79+
})
80+
})
81+
})

pkg/webhook/admission/http.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,9 @@ func addToScheme(scheme *runtime.Scheme) {
5050

5151
var _ http.Handler = &Webhook{}
5252

53+
// ContextKey is a type alias of string and is used as the key in context.
54+
type ContextKey string
55+
5356
func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
5457
var body []byte
5558
var err error
@@ -89,13 +92,11 @@ func (wh *Webhook) ServeHTTP(w http.ResponseWriter, r *http.Request) {
8992
}
9093

9194
// TODO: add panic-recovery for Handle
92-
type contextKey string
9395
ctx := context.Background()
9496
for k := range wh.KVMap {
95-
ctx = context.WithValue(ctx, contextKey(k), wh.KVMap[k])
97+
ctx = context.WithValue(ctx, ContextKey(k), wh.KVMap[k])
9698
}
9799
reviewResponse = wh.Handle(ctx, Request{AdmissionRequest: ar.Request})
98-
reviewResponse.Response.UID = ar.Request.UID
99100
writeResponse(w, reviewResponse)
100101
}
101102

pkg/webhook/admission/http_test.go

Lines changed: 191 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,191 @@
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 admission
18+
19+
import (
20+
"bytes"
21+
"context"
22+
"io"
23+
"net/http"
24+
25+
. "github.com/onsi/ginkgo"
26+
. "github.com/onsi/gomega"
27+
28+
admissionv1beta1 "k8s.io/api/admission/v1beta1"
29+
"sigs.k8s.io/controller-runtime/pkg/webhook/types"
30+
)
31+
32+
var _ = Describe("admission webhook http handler", func() {
33+
Describe("empty request body", func() {
34+
req := &http.Request{Body: nil}
35+
wh := &Webhook{
36+
Handlers: []Handler{},
37+
}
38+
w := &fakeResponseWriter{}
39+
expected := []byte(`{"response":{"uid":"","allowed":false,"status":{"metadata":{},"message":"request body is empty","code":400}}}
40+
`)
41+
It("should return a response with an error", func() {
42+
wh.ServeHTTP(w, req)
43+
Expect(w.response).NotTo(BeNil())
44+
Expect(w.response).To(Equal(expected))
45+
})
46+
})
47+
48+
Describe("wrong content type", func() {
49+
req := &http.Request{
50+
Header: http.Header{"Content-Type": []string{"application/foo"}},
51+
Body: nopCloser{Reader: bytes.NewBuffer(nil)},
52+
}
53+
wh := &Webhook{
54+
Handlers: []Handler{},
55+
}
56+
w := &fakeResponseWriter{}
57+
expected := []byte(`{"response":{"uid":"","allowed":false,"status":{"metadata":{},"message":"contentType=application/foo, expect application/json","code":400}}}
58+
`)
59+
It("should return a response with an error", func() {
60+
wh.ServeHTTP(w, req)
61+
Expect(w.response).NotTo(BeNil())
62+
Expect(w.response).To(Equal(expected))
63+
})
64+
})
65+
66+
Describe("can't decode body", func() {
67+
req := &http.Request{
68+
Header: http.Header{"Content-Type": []string{"application/json"}},
69+
Body: nopCloser{Reader: bytes.NewBufferString("{")},
70+
}
71+
wh := &Webhook{
72+
Type: types.WebhookTypeMutating,
73+
Handlers: []Handler{},
74+
}
75+
w := &fakeResponseWriter{}
76+
expected := []byte(
77+
`{"response":{"uid":"","allowed":false,"status":{"metadata":{},"message":"couldn't get version/kind; json parse error: unexpected end of JSON input","code":400}}}
78+
`)
79+
It("should return a response with an error", func() {
80+
wh.ServeHTTP(w, req)
81+
Expect(w.response).NotTo(BeNil())
82+
Expect(w.response).To(Equal(expected))
83+
})
84+
})
85+
86+
Describe("empty body after decoding", func() {
87+
req := &http.Request{
88+
Header: http.Header{"Content-Type": []string{"application/json"}},
89+
Body: nopCloser{Reader: bytes.NewBuffer(nil)},
90+
}
91+
wh := &Webhook{
92+
Type: types.WebhookTypeMutating,
93+
Handlers: []Handler{},
94+
}
95+
w := &fakeResponseWriter{}
96+
expected := []byte(`{"response":{"uid":"","allowed":false,"status":{"metadata":{},"message":"got an empty AdmissionRequest","code":400}}}
97+
`)
98+
It("should return a response with an error", func() {
99+
wh.ServeHTTP(w, req)
100+
Expect(w.response).NotTo(BeNil())
101+
Expect(w.response).To(Equal(expected))
102+
})
103+
})
104+
105+
Describe("no webhook type", func() {
106+
req := &http.Request{
107+
Header: http.Header{"Content-Type": []string{"application/json"}},
108+
Body: nopCloser{Reader: bytes.NewBufferString(`{"request":{}}`)},
109+
}
110+
wh := &Webhook{
111+
Handlers: []Handler{},
112+
}
113+
w := &fakeResponseWriter{}
114+
expected := []byte(`{"response":{"uid":"","allowed":false,"status":{"metadata":{},"message":"you must specify your webhook type","code":400}}}
115+
`)
116+
It("should return a response with an error", func() {
117+
wh.ServeHTTP(w, req)
118+
Expect(w.response).NotTo(BeNil())
119+
Expect(w.response).To(Equal(expected))
120+
})
121+
})
122+
123+
Describe("handler can be invoked", func() {
124+
req := &http.Request{
125+
Header: http.Header{"Content-Type": []string{"application/json"}},
126+
Body: nopCloser{Reader: bytes.NewBufferString(`{"request":{}}`)},
127+
}
128+
h := &fakeHandler{}
129+
wh := &Webhook{
130+
Type: types.WebhookTypeValidating,
131+
Handlers: []Handler{h},
132+
KVMap: map[string]interface{}{"foo": "bar"},
133+
}
134+
w := &fakeResponseWriter{}
135+
expected := []byte(`{"response":{"uid":"","allowed":true}}
136+
`)
137+
It("should return a response successfully", func() {
138+
wh.ServeHTTP(w, req)
139+
Expect(w.response).NotTo(BeNil())
140+
Expect(w.response).To(Equal(expected))
141+
Expect(h.invoked).To(BeTrue())
142+
Expect(h.valueFromContext).To(Equal("bar"))
143+
})
144+
})
145+
})
146+
147+
type fakeResponseWriter struct {
148+
response []byte
149+
}
150+
151+
var _ http.ResponseWriter = &fakeResponseWriter{}
152+
153+
func (w *fakeResponseWriter) Header() http.Header {
154+
return nil
155+
}
156+
157+
func (w *fakeResponseWriter) Write(resp []byte) (int, error) {
158+
w.response = append(w.response, resp...)
159+
return len(resp), nil
160+
}
161+
162+
func (w *fakeResponseWriter) WriteHeader(statusCode int) {}
163+
164+
type nopCloser struct {
165+
io.Reader
166+
}
167+
168+
func (nopCloser) Close() error { return nil }
169+
170+
type fakeHandler struct {
171+
invoked bool
172+
valueFromContext string
173+
fn func(context.Context, Request) Response
174+
}
175+
176+
func (h *fakeHandler) Handle(ctx context.Context, req Request) Response {
177+
v := ctx.Value(ContextKey("foo"))
178+
if v != nil {
179+
typed, ok := v.(string)
180+
if ok {
181+
h.valueFromContext = typed
182+
}
183+
}
184+
h.invoked = true
185+
if h.fn != nil {
186+
return h.fn(ctx, req)
187+
}
188+
return Response{Response: &admissionv1beta1.AdmissionResponse{
189+
Allowed: true,
190+
}}
191+
}

pkg/webhook/admission/response.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import (
2020
"net/http"
2121

2222
"github.com/mattbaird/jsonpatch"
23+
2324
admissionv1beta1 "k8s.io/api/admission/v1beta1"
2425
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
2526
"k8s.io/apimachinery/pkg/runtime"

0 commit comments

Comments
 (0)