Skip to content

Commit 5221bef

Browse files
author
Mengqi Yu
committed
add tests for webhook/admission pkg
1 parent 81820c5 commit 5221bef

File tree

9 files changed

+735
-15
lines changed

9 files changed

+735
-15
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 TestAdmissionWebhook(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: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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+
var decoder Decoder
31+
BeforeEach(func(done Done) {
32+
var err error
33+
decoder, err = NewDecoder(scheme.Scheme)
34+
Expect(err).NotTo(HaveOccurred())
35+
Expect(decoder).NotTo(BeNil())
36+
close(done)
37+
})
38+
39+
Describe("NewDecoder", func() {
40+
It("should return a decoder without an error", func() {
41+
decoder, err := NewDecoder(scheme.Scheme)
42+
Expect(err).NotTo(HaveOccurred())
43+
Expect(decoder).NotTo(BeNil())
44+
})
45+
})
46+
47+
Describe("Decode", func() {
48+
req := Request{
49+
AdmissionRequest: &admissionv1beta1.AdmissionRequest{
50+
Object: runtime.RawExtension{
51+
Raw: []byte(`{
52+
"apiVersion": "v1",
53+
"kind": "Pod",
54+
"metadata": {
55+
"name": "foo",
56+
"namespace": "default"
57+
},
58+
"spec": {
59+
"containers": [
60+
{
61+
"image": "bar",
62+
"name": "bar"
63+
}
64+
]
65+
}
66+
}`),
67+
},
68+
},
69+
}
70+
71+
It("should be able to decode", func() {
72+
err := decoder.Decode(req, &corev1.Pod{})
73+
Expect(err).NotTo(HaveOccurred())
74+
})
75+
76+
It("should return an error if the GVK mismatch", func() {
77+
err := decoder.Decode(req, &corev1.Node{})
78+
Expect(err).To(HaveOccurred())
79+
Expect(err.Error()).Should(ContainSubstring("unable to decode"))
80+
})
81+
})
82+
})

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

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)