...

Source file src/sigs.k8s.io/controller-runtime/pkg/webhook/admission/response_test.go

Documentation: sigs.k8s.io/controller-runtime/pkg/webhook/admission

     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  })
   250  

View as plain text