sigs.k8s.io/controller-runtime@v0.18.2/pkg/webhook/admission/response_test.go (about) 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 "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 })