1
16
17 package admission
18
19 import (
20 "errors"
21 "net/http"
22
23 . "github.com/onsi/ginkgo/v2"
24 . "github.com/onsi/gomega"
25
26 jsonpatch "gomodules.xyz/jsonpatch/v2"
27 admissionv1 "k8s.io/api/admission/v1"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 )
30
31 var _ = Describe("Admission Webhook Response Helpers", func() {
32 Describe("Allowed", func() {
33 It("should return an 'allowed' response", func() {
34 Expect(Allowed("")).To(Equal(
35 Response{
36 AdmissionResponse: admissionv1.AdmissionResponse{
37 Allowed: true,
38 Result: &metav1.Status{
39 Code: http.StatusOK,
40 },
41 },
42 },
43 ))
44 })
45
46 It("should populate a status with a reason when a reason is given", func() {
47 Expect(Allowed("acceptable")).To(Equal(
48 Response{
49 AdmissionResponse: admissionv1.AdmissionResponse{
50 Allowed: true,
51 Result: &metav1.Status{
52 Code: http.StatusOK,
53 Message: "acceptable",
54 },
55 },
56 },
57 ))
58 })
59 })
60
61 Describe("Denied", func() {
62 It("should return a 'not allowed' response", func() {
63 Expect(Denied("")).To(Equal(
64 Response{
65 AdmissionResponse: admissionv1.AdmissionResponse{
66 Allowed: false,
67 Result: &metav1.Status{
68 Code: http.StatusForbidden,
69 Reason: metav1.StatusReasonForbidden,
70 },
71 },
72 },
73 ))
74 })
75
76 It("should populate a status with a reason when a reason is given", func() {
77 Expect(Denied("UNACCEPTABLE!")).To(Equal(
78 Response{
79 AdmissionResponse: admissionv1.AdmissionResponse{
80 Allowed: false,
81 Result: &metav1.Status{
82 Code: http.StatusForbidden,
83 Reason: metav1.StatusReasonForbidden,
84 Message: "UNACCEPTABLE!",
85 },
86 },
87 },
88 ))
89 })
90 })
91
92 Describe("Patched", func() {
93 ops := []jsonpatch.JsonPatchOperation{
94 {
95 Operation: "replace",
96 Path: "/spec/selector/matchLabels",
97 Value: map[string]string{"foo": "bar"},
98 },
99 {
100 Operation: "delete",
101 Path: "/spec/replicas",
102 },
103 }
104 It("should return an 'allowed' response with the given patches", func() {
105 Expect(Patched("", ops...)).To(Equal(
106 Response{
107 AdmissionResponse: admissionv1.AdmissionResponse{
108 Allowed: true,
109 Result: &metav1.Status{
110 Code: http.StatusOK,
111 },
112 },
113 Patches: ops,
114 },
115 ))
116 })
117 It("should populate a status with a reason when a reason is given", func() {
118 Expect(Patched("some changes", ops...)).To(Equal(
119 Response{
120 AdmissionResponse: admissionv1.AdmissionResponse{
121 Allowed: true,
122 Result: &metav1.Status{
123 Code: http.StatusOK,
124 Message: "some changes",
125 },
126 },
127 Patches: ops,
128 },
129 ))
130 })
131 })
132
133 Describe("Errored", func() {
134 It("should return a denied response with an error", func() {
135 err := errors.New("this is an error")
136 expected := Response{
137 AdmissionResponse: admissionv1.AdmissionResponse{
138 Allowed: false,
139 Result: &metav1.Status{
140 Code: http.StatusBadRequest,
141 Message: err.Error(),
142 },
143 },
144 }
145 resp := Errored(http.StatusBadRequest, err)
146 Expect(resp).To(Equal(expected))
147 })
148 })
149
150 Describe("ValidationResponse", func() {
151 It("should populate a status with a message when a message is given", func() {
152 By("checking that a message is populated for 'allowed' responses")
153 Expect(ValidationResponse(true, "acceptable")).To(Equal(
154 Response{
155 AdmissionResponse: admissionv1.AdmissionResponse{
156 Allowed: true,
157 Result: &metav1.Status{
158 Code: http.StatusOK,
159 Message: "acceptable",
160 },
161 },
162 },
163 ))
164
165 By("checking that a message is populated for 'denied' responses")
166 Expect(ValidationResponse(false, "UNACCEPTABLE!")).To(Equal(
167 Response{
168 AdmissionResponse: admissionv1.AdmissionResponse{
169 Allowed: false,
170 Result: &metav1.Status{
171 Code: http.StatusForbidden,
172 Reason: metav1.StatusReasonForbidden,
173 Message: "UNACCEPTABLE!",
174 },
175 },
176 },
177 ))
178 })
179
180 It("should return an admission decision", func() {
181 By("checking that it returns an 'allowed' response when allowed is true")
182 Expect(ValidationResponse(true, "")).To(Equal(
183 Response{
184 AdmissionResponse: admissionv1.AdmissionResponse{
185 Allowed: true,
186 Result: &metav1.Status{
187 Code: http.StatusOK,
188 },
189 },
190 },
191 ))
192
193 By("checking that it returns an 'denied' response when allowed is false")
194 Expect(ValidationResponse(false, "")).To(Equal(
195 Response{
196 AdmissionResponse: admissionv1.AdmissionResponse{
197 Allowed: false,
198 Result: &metav1.Status{
199 Code: http.StatusForbidden,
200 Reason: metav1.StatusReasonForbidden,
201 },
202 },
203 },
204 ))
205 })
206 })
207
208 Describe("PatchResponseFromRaw", func() {
209 It("should return an 'allowed' response with a patch of the diff between two sets of serialized JSON", func() {
210 expected := Response{
211 Patches: []jsonpatch.JsonPatchOperation{
212 {Operation: "replace", Path: "/a", Value: "bar"},
213 },
214 AdmissionResponse: admissionv1.AdmissionResponse{
215 Allowed: true,
216 PatchType: func() *admissionv1.PatchType { pt := admissionv1.PatchTypeJSONPatch; return &pt }(),
217 },
218 }
219 resp := PatchResponseFromRaw([]byte(`{"a": "foo"}`), []byte(`{"a": "bar"}`))
220 Expect(resp).To(Equal(expected))
221 })
222 })
223
224 Describe("WithWarnings", func() {
225 It("should add the warnings to the existing response without removing any existing warnings", func() {
226 initialResponse := Response{
227 AdmissionResponse: admissionv1.AdmissionResponse{
228 Allowed: true,
229 Result: &metav1.Status{
230 Code: http.StatusOK,
231 },
232 Warnings: []string{"existing-warning"},
233 },
234 }
235 warnings := []string{"additional-warning-1", "additional-warning-2"}
236 expectedResponse := Response{
237 AdmissionResponse: admissionv1.AdmissionResponse{
238 Allowed: true,
239 Result: &metav1.Status{
240 Code: http.StatusOK,
241 },
242 Warnings: []string{"existing-warning", "additional-warning-1", "additional-warning-2"},
243 },
244 }
245
246 Expect(initialResponse.WithWarnings(warnings...)).To(Equal(expectedResponse))
247 })
248 })
249 })
250
View as plain text