agones.dev/agones@v1.54.0/pkg/util/webhooks/webhooks_test.go (about)

     1  // Copyright 2018 Google LLC All Rights Reserved.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package webhooks
    16  
    17  import (
    18  	"bytes"
    19  	"encoding/json"
    20  	"io"
    21  	"net/http"
    22  	"net/http/httptest"
    23  	"testing"
    24  
    25  	"github.com/pkg/errors"
    26  	"github.com/stretchr/testify/assert"
    27  	admissionv1 "k8s.io/api/admission/v1"
    28  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    29  	"k8s.io/apimachinery/pkg/runtime"
    30  	"k8s.io/apimachinery/pkg/runtime/schema"
    31  
    32  	agonesv1 "agones.dev/agones/pkg/apis/agones/v1"
    33  )
    34  
    35  func TestWebHookAddHandler(t *testing.T) {
    36  	t.Parallel()
    37  
    38  	type testHandler struct {
    39  		gk schema.GroupKind
    40  		op admissionv1.Operation
    41  	}
    42  	type expected struct {
    43  		count int
    44  	}
    45  	fixtures := map[string]struct {
    46  		handlers []testHandler
    47  		expected expected
    48  	}{
    49  		"single, matching": {
    50  			handlers: []testHandler{{gk: schema.GroupKind{Group: "group", Kind: "kind"}, op: admissionv1.Create}},
    51  			expected: expected{count: 1},
    52  		},
    53  		"double, one matching op": {
    54  			expected: expected{count: 1},
    55  			handlers: []testHandler{
    56  				{gk: schema.GroupKind{Group: "group", Kind: "kind"}, op: admissionv1.Create},
    57  				{gk: schema.GroupKind{Group: "group", Kind: "kind"}, op: admissionv1.Update},
    58  			},
    59  		},
    60  		"double, one matching group": {
    61  			expected: expected{count: 1},
    62  			handlers: []testHandler{
    63  				{gk: schema.GroupKind{Group: "group", Kind: "kind"}, op: admissionv1.Create},
    64  				{gk: schema.GroupKind{Group: "nope", Kind: "kind"}, op: admissionv1.Create},
    65  			},
    66  		},
    67  		"double, one matching kind": {
    68  			expected: expected{count: 1},
    69  			handlers: []testHandler{
    70  				{gk: schema.GroupKind{Group: "group", Kind: "kind"}, op: admissionv1.Create},
    71  				{gk: schema.GroupKind{Group: "group", Kind: "nope"}, op: admissionv1.Create},
    72  			},
    73  		},
    74  		"double, both matching": {
    75  			expected: expected{count: 2},
    76  			handlers: []testHandler{
    77  				{gk: schema.GroupKind{Group: "group", Kind: "kind"}, op: admissionv1.Create},
    78  				{gk: schema.GroupKind{Group: "group", Kind: "kind"}, op: admissionv1.Create},
    79  			},
    80  		},
    81  	}
    82  
    83  	for k, handles := range fixtures {
    84  		t.Run(k, func(t *testing.T) {
    85  
    86  			stop := make(chan struct{})
    87  			defer close(stop)
    88  
    89  			fixture := admissionv1.AdmissionReview{Request: &admissionv1.AdmissionRequest{
    90  				Kind:      metav1.GroupVersionKind{Kind: "kind", Group: "group", Version: "version"},
    91  				Operation: admissionv1.Create,
    92  				UID:       "1234"}}
    93  
    94  			callCount := 0
    95  			mux := http.NewServeMux()
    96  			ts := httptest.NewUnstartedServer(mux)
    97  			wh := NewWebHook(mux)
    98  
    99  			for _, th := range handles.handlers {
   100  				wh.AddHandler("/test", th.gk, th.op, func(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error) {
   101  					assert.Equal(t, fixture.Request, review.Request)
   102  					assert.True(t, review.Response.Allowed)
   103  					callCount++
   104  					return review, nil
   105  				})
   106  			}
   107  
   108  			ts.StartTLS()
   109  			defer ts.Close()
   110  
   111  			client := ts.Client()
   112  			url := ts.URL + "/test"
   113  
   114  			buf := &bytes.Buffer{}
   115  			err := json.NewEncoder(buf).Encode(fixture)
   116  			assert.Nil(t, err)
   117  
   118  			r, err := http.NewRequest("GET", url, buf)
   119  			assert.Nil(t, err)
   120  
   121  			resp, err := client.Do(r)
   122  			assert.Nil(t, err)
   123  			defer resp.Body.Close() // nolint: errcheck
   124  			assert.Equal(t, http.StatusOK, resp.StatusCode)
   125  
   126  			assert.Equal(t, handles.expected.count, callCount, "[%v] /test should have been called for %#v", k, handles)
   127  		})
   128  	}
   129  }
   130  
   131  func TestWebHookFleetValidationHandler(t *testing.T) {
   132  	t.Parallel()
   133  
   134  	type testHandler struct {
   135  		gk schema.GroupKind
   136  		op admissionv1.Operation
   137  	}
   138  	type expected struct {
   139  		count int
   140  	}
   141  	fixtures := map[string]struct {
   142  		handlers []testHandler
   143  		expected expected
   144  	}{
   145  		"single, matching": {
   146  			handlers: []testHandler{{gk: schema.GroupKind{Group: "group", Kind: "fleet"}, op: admissionv1.Create}},
   147  			expected: expected{count: 1},
   148  		},
   149  	}
   150  
   151  	for k, handles := range fixtures {
   152  		t.Run(k, func(t *testing.T) {
   153  
   154  			stop := make(chan struct{})
   155  			defer close(stop)
   156  
   157  			raw := []byte(`{
   158  				"apiVersion": "agones.dev/v1",
   159  				"kind": "Fleet",
   160  				"spec": {
   161  					"replicas": 2,
   162  					"template": {
   163  						"spec": {
   164  							"template": {
   165  								"spec": {
   166  									"containers": [{
   167  										"image": "us-docker.pkg.dev/agones-images/examples/simple-game-server:0.39",
   168  										"name": false
   169  									}]
   170  								}
   171  							}
   172  						}
   173  					}
   174  				}
   175  			}`)
   176  			fixture := admissionv1.AdmissionReview{Request: &admissionv1.AdmissionRequest{
   177  				Kind:      metav1.GroupVersionKind{Kind: "fleet", Group: "group", Version: "version"},
   178  				Operation: admissionv1.Create,
   179  				Object: runtime.RawExtension{
   180  					Raw: raw,
   181  				},
   182  				UID: "1234"}}
   183  
   184  			callCount := 0
   185  			mux := http.NewServeMux()
   186  			ts := httptest.NewUnstartedServer(mux)
   187  			wh := NewWebHook(mux)
   188  
   189  			for _, th := range handles.handlers {
   190  				wh.AddHandler("/test", th.gk, th.op, func(review admissionv1.AdmissionReview) (admissionv1.AdmissionReview, error) {
   191  					fleet := &agonesv1.Fleet{}
   192  
   193  					callCount++
   194  					obj := review.Request.Object
   195  					err := json.Unmarshal(obj.Raw, fleet)
   196  					assert.NotNil(t, err)
   197  					if err != nil {
   198  						return review, errors.Wrapf(err, "error unmarshalling original Fleet json: %s", obj.Raw)
   199  					}
   200  					return review, nil
   201  				})
   202  			}
   203  
   204  			ts.StartTLS()
   205  			defer ts.Close()
   206  
   207  			client := ts.Client()
   208  			url := ts.URL + "/test"
   209  
   210  			buf := &bytes.Buffer{}
   211  			err := json.NewEncoder(buf).Encode(fixture)
   212  			assert.Nil(t, err)
   213  
   214  			r, err := http.NewRequest("GET", url, buf)
   215  			assert.Nil(t, err)
   216  
   217  			resp, err := client.Do(r)
   218  			assert.Nil(t, err)
   219  			defer resp.Body.Close() // nolint: errcheck
   220  			assert.Equal(t, http.StatusOK, resp.StatusCode)
   221  			body, err := io.ReadAll(resp.Body)
   222  			assert.Nil(t, err)
   223  
   224  			expected := "cannot unmarshal bool into Go struct field Container.spec.template.spec.template.spec.containers.name of type string"
   225  			assert.Contains(t, string(body), expected)
   226  
   227  			assert.Equal(t, handles.expected.count, callCount, "[%v] /test should have been called for %#v", k, handles)
   228  		})
   229  	}
   230  }