k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/apis/admissionregistration/validation/validation_test.go (about)

     1  /*
     2  Copyright 2017 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 validation
    18  
    19  import (
    20  	"fmt"
    21  	"strings"
    22  	"testing"
    23  
    24  	"github.com/google/cel-go/cel"
    25  
    26  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/apimachinery/pkg/util/validation/field"
    28  	"k8s.io/apimachinery/pkg/util/version"
    29  	plugincel "k8s.io/apiserver/pkg/admission/plugin/cel"
    30  	"k8s.io/apiserver/pkg/cel/environment"
    31  	"k8s.io/apiserver/pkg/cel/library"
    32  	"k8s.io/apiserver/pkg/features"
    33  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    34  	"k8s.io/kubernetes/pkg/apis/admissionregistration"
    35  )
    36  
    37  func ptr[T any](v T) *T { return &v }
    38  
    39  func strPtr(s string) *string { return &s }
    40  
    41  func int32Ptr(i int32) *int32 { return &i }
    42  
    43  func newValidatingWebhookConfiguration(hooks []admissionregistration.ValidatingWebhook, defaultAdmissionReviewVersions bool) *admissionregistration.ValidatingWebhookConfiguration {
    44  	// If the test case did not specify an AdmissionReviewVersions, default it so the test passes as
    45  	// this field will be defaulted in production code.
    46  	for i := range hooks {
    47  		if defaultAdmissionReviewVersions && len(hooks[i].AdmissionReviewVersions) == 0 {
    48  			hooks[i].AdmissionReviewVersions = []string{"v1beta1"}
    49  		}
    50  	}
    51  	return &admissionregistration.ValidatingWebhookConfiguration{
    52  		ObjectMeta: metav1.ObjectMeta{
    53  			Name: "config",
    54  		},
    55  		Webhooks: hooks,
    56  	}
    57  }
    58  
    59  func TestValidateValidatingWebhookConfiguration(t *testing.T) {
    60  	noSideEffect := admissionregistration.SideEffectClassNone
    61  	unknownSideEffect := admissionregistration.SideEffectClassUnknown
    62  	validClientConfig := admissionregistration.WebhookClientConfig{
    63  		URL: strPtr("https://example.com"),
    64  	}
    65  	tests := []struct {
    66  		name          string
    67  		config        *admissionregistration.ValidatingWebhookConfiguration
    68  		expectedError string
    69  	}{{
    70  		name: "AdmissionReviewVersions are required",
    71  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
    72  			Name:         "webhook.k8s.io",
    73  			ClientConfig: validClientConfig,
    74  			SideEffects:  &unknownSideEffect,
    75  		},
    76  		}, false),
    77  		expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`,
    78  	}, {
    79  		name: "should fail on bad AdmissionReviewVersion value",
    80  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
    81  			Name:                    "webhook.k8s.io",
    82  			ClientConfig:            validClientConfig,
    83  			AdmissionReviewVersions: []string{"0v"},
    84  		},
    85  		}, true),
    86  		expectedError: `Invalid value: "0v": a DNS-1035 label`,
    87  	}, {
    88  		name: "should pass on valid AdmissionReviewVersion",
    89  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
    90  			Name:                    "webhook.k8s.io",
    91  			ClientConfig:            validClientConfig,
    92  			SideEffects:             &noSideEffect,
    93  			AdmissionReviewVersions: []string{"v1beta1"},
    94  		},
    95  		}, true),
    96  		expectedError: ``,
    97  	}, {
    98  		name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion",
    99  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   100  			Name:                    "webhook.k8s.io",
   101  			ClientConfig:            validClientConfig,
   102  			SideEffects:             &noSideEffect,
   103  			AdmissionReviewVersions: []string{"v1beta1", "invalid-version"},
   104  		},
   105  		}, true),
   106  		expectedError: ``,
   107  	}, {
   108  		name: "should fail on invalid AdmissionReviewVersion",
   109  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   110  			Name:                    "webhook.k8s.io",
   111  			ClientConfig:            validClientConfig,
   112  			AdmissionReviewVersions: []string{"invalidVersion"},
   113  		},
   114  		}, true),
   115  		expectedError: `Invalid value: []string{"invalidVersion"}`,
   116  	}, {
   117  		name: "should fail on duplicate AdmissionReviewVersion",
   118  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   119  			Name:                    "webhook.k8s.io",
   120  			ClientConfig:            validClientConfig,
   121  			AdmissionReviewVersions: []string{"v1beta1", "v1beta1"},
   122  		},
   123  		}, true),
   124  		expectedError: `Invalid value: "v1beta1": duplicate version`,
   125  	}, {
   126  		name: "all Webhooks must have a fully qualified name",
   127  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   128  			Name:         "webhook.k8s.io",
   129  			ClientConfig: validClientConfig,
   130  			SideEffects:  &noSideEffect,
   131  		}, {
   132  			Name:         "k8s.io",
   133  			ClientConfig: validClientConfig,
   134  			SideEffects:  &noSideEffect,
   135  		}, {
   136  			Name:         "",
   137  			ClientConfig: validClientConfig,
   138  			SideEffects:  &noSideEffect,
   139  		},
   140  		}, true),
   141  		expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`,
   142  	}, {
   143  		name: "Webhooks must have unique names when created",
   144  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   145  			Name:         "webhook.k8s.io",
   146  			ClientConfig: validClientConfig,
   147  			SideEffects:  &unknownSideEffect,
   148  		}, {
   149  			Name:         "webhook.k8s.io",
   150  			ClientConfig: validClientConfig,
   151  			SideEffects:  &unknownSideEffect,
   152  		},
   153  		}, true),
   154  		expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`,
   155  	}, {
   156  		name: "Operations must not be empty or nil",
   157  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   158  			Name: "webhook.k8s.io",
   159  			Rules: []admissionregistration.RuleWithOperations{{
   160  				Operations: []admissionregistration.OperationType{},
   161  				Rule: admissionregistration.Rule{
   162  					APIGroups:   []string{"a"},
   163  					APIVersions: []string{"a"},
   164  					Resources:   []string{"a"},
   165  				},
   166  			}, {
   167  				Operations: nil,
   168  				Rule: admissionregistration.Rule{
   169  					APIGroups:   []string{"a"},
   170  					APIVersions: []string{"a"},
   171  					Resources:   []string{"a"},
   172  				},
   173  			}},
   174  		},
   175  		}, true),
   176  		expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`,
   177  	}, {
   178  		name: "\"\" is NOT a valid operation",
   179  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   180  			Name: "webhook.k8s.io",
   181  			Rules: []admissionregistration.RuleWithOperations{{
   182  				Operations: []admissionregistration.OperationType{"CREATE", ""},
   183  				Rule: admissionregistration.Rule{
   184  					APIGroups:   []string{"a"},
   185  					APIVersions: []string{"a"},
   186  					Resources:   []string{"a"},
   187  				},
   188  			}},
   189  		},
   190  		}, true),
   191  		expectedError: `Unsupported value: ""`,
   192  	}, {
   193  		name: "operation must be either create/update/delete/connect",
   194  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   195  			Name: "webhook.k8s.io",
   196  			Rules: []admissionregistration.RuleWithOperations{{
   197  				Operations: []admissionregistration.OperationType{"PATCH"},
   198  				Rule: admissionregistration.Rule{
   199  					APIGroups:   []string{"a"},
   200  					APIVersions: []string{"a"},
   201  					Resources:   []string{"a"},
   202  				},
   203  			}},
   204  		},
   205  		}, true),
   206  		expectedError: `Unsupported value: "PATCH"`,
   207  	}, {
   208  		name: "wildcard operation cannot be mixed with other strings",
   209  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   210  			Name: "webhook.k8s.io",
   211  			Rules: []admissionregistration.RuleWithOperations{{
   212  				Operations: []admissionregistration.OperationType{"CREATE", "*"},
   213  				Rule: admissionregistration.Rule{
   214  					APIGroups:   []string{"a"},
   215  					APIVersions: []string{"a"},
   216  					Resources:   []string{"a"},
   217  				},
   218  			}},
   219  		},
   220  		}, true),
   221  		expectedError: `if '*' is present, must not specify other operations`,
   222  	}, {
   223  		name: `resource "*" can co-exist with resources that have subresources`,
   224  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   225  			Name:         "webhook.k8s.io",
   226  			ClientConfig: validClientConfig,
   227  			SideEffects:  &noSideEffect,
   228  			Rules: []admissionregistration.RuleWithOperations{{
   229  				Operations: []admissionregistration.OperationType{"CREATE"},
   230  				Rule: admissionregistration.Rule{
   231  					APIGroups:   []string{"a"},
   232  					APIVersions: []string{"a"},
   233  					Resources:   []string{"*", "a/b", "a/*", "*/b"},
   234  				},
   235  			}},
   236  		},
   237  		}, true),
   238  	}, {
   239  		name: `resource "*" cannot mix with resources that don't have subresources`,
   240  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   241  			Name:         "webhook.k8s.io",
   242  			ClientConfig: validClientConfig,
   243  			SideEffects:  &unknownSideEffect,
   244  			Rules: []admissionregistration.RuleWithOperations{{
   245  				Operations: []admissionregistration.OperationType{"CREATE"},
   246  				Rule: admissionregistration.Rule{
   247  					APIGroups:   []string{"a"},
   248  					APIVersions: []string{"a"},
   249  					Resources:   []string{"*", "a"},
   250  				},
   251  			}},
   252  		},
   253  		}, true),
   254  		expectedError: `if '*' is present, must not specify other resources without subresources`,
   255  	}, {
   256  		name: "resource a/* cannot mix with a/x",
   257  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   258  			Name:         "webhook.k8s.io",
   259  			ClientConfig: validClientConfig,
   260  			SideEffects:  &unknownSideEffect,
   261  			Rules: []admissionregistration.RuleWithOperations{{
   262  				Operations: []admissionregistration.OperationType{"CREATE"},
   263  				Rule: admissionregistration.Rule{
   264  					APIGroups:   []string{"a"},
   265  					APIVersions: []string{"a"},
   266  					Resources:   []string{"a/*", "a/x"},
   267  				},
   268  			}},
   269  		},
   270  		}, true),
   271  		expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`,
   272  	}, {
   273  		name: "resource a/* can mix with a",
   274  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   275  			Name:         "webhook.k8s.io",
   276  			ClientConfig: validClientConfig,
   277  			SideEffects:  &noSideEffect,
   278  			Rules: []admissionregistration.RuleWithOperations{{
   279  				Operations: []admissionregistration.OperationType{"CREATE"},
   280  				Rule: admissionregistration.Rule{
   281  					APIGroups:   []string{"a"},
   282  					APIVersions: []string{"a"},
   283  					Resources:   []string{"a/*", "a"},
   284  				},
   285  			}},
   286  		},
   287  		}, true),
   288  	}, {
   289  		name: "resource */a cannot mix with x/a",
   290  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   291  			Name:         "webhook.k8s.io",
   292  			ClientConfig: validClientConfig,
   293  			SideEffects:  &unknownSideEffect,
   294  			Rules: []admissionregistration.RuleWithOperations{{
   295  				Operations: []admissionregistration.OperationType{"CREATE"},
   296  				Rule: admissionregistration.Rule{
   297  					APIGroups:   []string{"a"},
   298  					APIVersions: []string{"a"},
   299  					Resources:   []string{"*/a", "x/a"},
   300  				},
   301  			}},
   302  		},
   303  		}, true),
   304  		expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`,
   305  	}, {
   306  		name: "resource */* cannot mix with other resources",
   307  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   308  			Name:         "webhook.k8s.io",
   309  			ClientConfig: validClientConfig,
   310  			SideEffects:  &unknownSideEffect,
   311  			Rules: []admissionregistration.RuleWithOperations{{
   312  				Operations: []admissionregistration.OperationType{"CREATE"},
   313  				Rule: admissionregistration.Rule{
   314  					APIGroups:   []string{"a"},
   315  					APIVersions: []string{"a"},
   316  					Resources:   []string{"*/*", "a"},
   317  				},
   318  			}},
   319  		},
   320  		}, true),
   321  		expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
   322  	}, {
   323  		name: "FailurePolicy can only be \"Ignore\" or \"Fail\"",
   324  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   325  			Name:         "webhook.k8s.io",
   326  			ClientConfig: validClientConfig,
   327  			SideEffects:  &unknownSideEffect,
   328  			FailurePolicy: func() *admissionregistration.FailurePolicyType {
   329  				r := admissionregistration.FailurePolicyType("other")
   330  				return &r
   331  			}(),
   332  		},
   333  		}, true),
   334  		expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
   335  	}, {
   336  		name: "AdmissionReviewVersions are required",
   337  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   338  			Name:         "webhook.k8s.io",
   339  			ClientConfig: validClientConfig,
   340  			SideEffects:  &unknownSideEffect,
   341  		},
   342  		}, false),
   343  		expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`,
   344  	}, {
   345  		name: "SideEffects are required",
   346  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   347  			Name:         "webhook.k8s.io",
   348  			ClientConfig: validClientConfig,
   349  			SideEffects:  nil,
   350  		},
   351  		}, true),
   352  		expectedError: `webhooks[0].sideEffects: Required value: must specify one of None, NoneOnDryRun`,
   353  	}, {
   354  		name: "SideEffects can only be \"None\" or \"NoneOnDryRun\" when created",
   355  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   356  			Name:         "webhook.k8s.io",
   357  			ClientConfig: validClientConfig,
   358  			SideEffects: func() *admissionregistration.SideEffectClass {
   359  				r := admissionregistration.SideEffectClass("other")
   360  				return &r
   361  			}(),
   362  		},
   363  		}, true),
   364  		expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun"`,
   365  	}, {
   366  		name: "both service and URL missing",
   367  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   368  			Name:         "webhook.k8s.io",
   369  			ClientConfig: admissionregistration.WebhookClientConfig{},
   370  		},
   371  		}, true),
   372  		expectedError: `exactly one of`,
   373  	}, {
   374  		name: "both service and URL provided",
   375  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   376  			Name: "webhook.k8s.io",
   377  			ClientConfig: admissionregistration.WebhookClientConfig{
   378  				Service: &admissionregistration.ServiceReference{
   379  					Namespace: "ns",
   380  					Name:      "n",
   381  					Port:      443,
   382  				},
   383  				URL: strPtr("example.com/k8s/webhook"),
   384  			},
   385  		},
   386  		}, true),
   387  		expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`,
   388  	}, {
   389  		name: "blank URL",
   390  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   391  			Name: "webhook.k8s.io",
   392  			ClientConfig: admissionregistration.WebhookClientConfig{
   393  				URL: strPtr(""),
   394  			},
   395  		},
   396  		}, true),
   397  		expectedError: `[0].clientConfig.url: Invalid value: "": host must be specified`,
   398  	}, {
   399  		name: "wrong scheme",
   400  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   401  			Name: "webhook.k8s.io",
   402  			ClientConfig: admissionregistration.WebhookClientConfig{
   403  				URL: strPtr("http://example.com"),
   404  			},
   405  		},
   406  		}, true),
   407  		expectedError: `https`,
   408  	}, {
   409  		name: "missing host",
   410  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   411  			Name: "webhook.k8s.io",
   412  			ClientConfig: admissionregistration.WebhookClientConfig{
   413  				URL: strPtr("https:///fancy/webhook"),
   414  			},
   415  		},
   416  		}, true),
   417  		expectedError: `host must be specified`,
   418  	}, {
   419  		name: "fragment",
   420  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   421  			Name: "webhook.k8s.io",
   422  			ClientConfig: admissionregistration.WebhookClientConfig{
   423  				URL: strPtr("https://example.com/#bookmark"),
   424  			},
   425  		},
   426  		}, true),
   427  		expectedError: `"bookmark": fragments are not permitted`,
   428  	}, {
   429  		name: "query",
   430  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   431  			Name: "webhook.k8s.io",
   432  			ClientConfig: admissionregistration.WebhookClientConfig{
   433  				URL: strPtr("https://example.com?arg=value"),
   434  			},
   435  		},
   436  		}, true),
   437  		expectedError: `"arg=value": query parameters are not permitted`,
   438  	}, {
   439  		name: "user",
   440  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   441  			Name: "webhook.k8s.io",
   442  			ClientConfig: admissionregistration.WebhookClientConfig{
   443  				URL: strPtr("https://harry.potter@example.com/"),
   444  			},
   445  		},
   446  		}, true),
   447  		expectedError: `"harry.potter": user information is not permitted`,
   448  	}, {
   449  		name: "just totally wrong",
   450  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   451  			Name: "webhook.k8s.io",
   452  			ClientConfig: admissionregistration.WebhookClientConfig{
   453  				URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"),
   454  			},
   455  		},
   456  		}, true),
   457  		expectedError: `host must be specified`,
   458  	}, {
   459  		name: "path must start with slash",
   460  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   461  			Name: "webhook.k8s.io",
   462  			ClientConfig: admissionregistration.WebhookClientConfig{
   463  				Service: &admissionregistration.ServiceReference{
   464  					Namespace: "ns",
   465  					Name:      "n",
   466  					Path:      strPtr("foo/"),
   467  					Port:      443,
   468  				},
   469  			},
   470  		},
   471  		}, true),
   472  		expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`,
   473  	}, {
   474  		name: "path accepts slash",
   475  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   476  			Name: "webhook.k8s.io",
   477  			ClientConfig: admissionregistration.WebhookClientConfig{
   478  				Service: &admissionregistration.ServiceReference{
   479  					Namespace: "ns",
   480  					Name:      "n",
   481  					Path:      strPtr("/"),
   482  					Port:      443,
   483  				},
   484  			},
   485  			SideEffects: &noSideEffect,
   486  		},
   487  		}, true),
   488  		expectedError: ``,
   489  	}, {
   490  		name: "path accepts no trailing slash",
   491  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   492  			Name: "webhook.k8s.io",
   493  			ClientConfig: admissionregistration.WebhookClientConfig{
   494  				Service: &admissionregistration.ServiceReference{
   495  					Namespace: "ns",
   496  					Name:      "n",
   497  					Path:      strPtr("/foo"),
   498  					Port:      443,
   499  				},
   500  			},
   501  			SideEffects: &noSideEffect,
   502  		},
   503  		}, true),
   504  		expectedError: ``,
   505  	}, {
   506  		name: "path fails //",
   507  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   508  			Name: "webhook.k8s.io",
   509  			ClientConfig: admissionregistration.WebhookClientConfig{
   510  				Service: &admissionregistration.ServiceReference{
   511  					Namespace: "ns",
   512  					Name:      "n",
   513  					Path:      strPtr("//"),
   514  					Port:      443,
   515  				},
   516  			},
   517  			SideEffects: &noSideEffect,
   518  		},
   519  		}, true),
   520  		expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`,
   521  	}, {
   522  		name: "path no empty step",
   523  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   524  			Name: "webhook.k8s.io",
   525  			ClientConfig: admissionregistration.WebhookClientConfig{
   526  				Service: &admissionregistration.ServiceReference{
   527  					Namespace: "ns",
   528  					Name:      "n",
   529  					Path:      strPtr("/foo//bar/"),
   530  					Port:      443,
   531  				},
   532  			},
   533  			SideEffects: &unknownSideEffect,
   534  		},
   535  		}, true),
   536  		expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`,
   537  	}, {
   538  		name: "path no empty step 2",
   539  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   540  			Name: "webhook.k8s.io",
   541  			ClientConfig: admissionregistration.WebhookClientConfig{
   542  				Service: &admissionregistration.ServiceReference{
   543  					Namespace: "ns",
   544  					Name:      "n",
   545  					Path:      strPtr("/foo/bar//"),
   546  					Port:      443,
   547  				},
   548  			},
   549  			SideEffects: &unknownSideEffect,
   550  		},
   551  		}, true),
   552  		expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`,
   553  	}, {
   554  		name: "path no non-subdomain",
   555  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   556  			Name: "webhook.k8s.io",
   557  			ClientConfig: admissionregistration.WebhookClientConfig{
   558  				Service: &admissionregistration.ServiceReference{
   559  					Namespace: "ns",
   560  					Name:      "n",
   561  					Path:      strPtr("/apis/foo.bar/v1alpha1/--bad"),
   562  					Port:      443,
   563  				},
   564  			},
   565  			SideEffects: &unknownSideEffect,
   566  		},
   567  		}, true),
   568  		expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a lowercase RFC 1123 subdomain`,
   569  	}, {
   570  		name: "invalid port 0",
   571  		config: newValidatingWebhookConfiguration(
   572  			[]admissionregistration.ValidatingWebhook{{
   573  				Name: "webhook.k8s.io",
   574  				ClientConfig: admissionregistration.WebhookClientConfig{
   575  					Service: &admissionregistration.ServiceReference{
   576  						Namespace: "ns",
   577  						Name:      "n",
   578  						Path:      strPtr("https://apis/foo.bar"),
   579  						Port:      0,
   580  					},
   581  				},
   582  				SideEffects: &unknownSideEffect,
   583  			},
   584  			}, true),
   585  		expectedError: `Invalid value: 0: port is not valid: must be between 1 and 65535, inclusive`,
   586  	}, {
   587  		name: "invalid port >65535",
   588  		config: newValidatingWebhookConfiguration(
   589  			[]admissionregistration.ValidatingWebhook{{
   590  				Name: "webhook.k8s.io",
   591  				ClientConfig: admissionregistration.WebhookClientConfig{
   592  					Service: &admissionregistration.ServiceReference{
   593  						Namespace: "ns",
   594  						Name:      "n",
   595  						Path:      strPtr("https://apis/foo.bar"),
   596  						Port:      65536,
   597  					},
   598  				},
   599  				SideEffects: &unknownSideEffect,
   600  			},
   601  			}, true),
   602  		expectedError: `Invalid value: 65536: port is not valid: must be between 1 and 65535, inclusive`,
   603  	}, {
   604  		name: "timeout seconds cannot be greater than 30",
   605  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   606  			Name:           "webhook.k8s.io",
   607  			ClientConfig:   validClientConfig,
   608  			SideEffects:    &unknownSideEffect,
   609  			TimeoutSeconds: int32Ptr(31),
   610  		},
   611  		}, true),
   612  		expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`,
   613  	}, {
   614  		name: "timeout seconds cannot be smaller than 1",
   615  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   616  			Name:           "webhook.k8s.io",
   617  			ClientConfig:   validClientConfig,
   618  			SideEffects:    &unknownSideEffect,
   619  			TimeoutSeconds: int32Ptr(0),
   620  		},
   621  		}, true),
   622  		expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`,
   623  	}, {
   624  		name: "timeout seconds must be positive",
   625  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   626  			Name:           "webhook.k8s.io",
   627  			ClientConfig:   validClientConfig,
   628  			SideEffects:    &unknownSideEffect,
   629  			TimeoutSeconds: int32Ptr(-1),
   630  		},
   631  		}, true),
   632  		expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`,
   633  	}, {
   634  		name: "valid timeout seconds",
   635  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   636  			Name:           "webhook.k8s.io",
   637  			ClientConfig:   validClientConfig,
   638  			SideEffects:    &noSideEffect,
   639  			TimeoutSeconds: int32Ptr(1),
   640  		}, {
   641  			Name:           "webhook2.k8s.io",
   642  			ClientConfig:   validClientConfig,
   643  			SideEffects:    &noSideEffect,
   644  			TimeoutSeconds: int32Ptr(15),
   645  		}, {
   646  			Name:           "webhook3.k8s.io",
   647  			ClientConfig:   validClientConfig,
   648  			SideEffects:    &noSideEffect,
   649  			TimeoutSeconds: int32Ptr(30),
   650  		},
   651  		}, true),
   652  	}, {
   653  		name: "single match condition must have a name",
   654  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   655  			Name:         "webhook.k8s.io",
   656  			ClientConfig: validClientConfig,
   657  			SideEffects:  &noSideEffect,
   658  			MatchConditions: []admissionregistration.MatchCondition{{
   659  				Expression: "true",
   660  			}},
   661  		},
   662  		}, true),
   663  		expectedError: `webhooks[0].matchConditions[0].name: Required value`,
   664  	}, {
   665  		name: "all match conditions must have a name",
   666  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   667  			Name:         "webhook.k8s.io",
   668  			ClientConfig: validClientConfig,
   669  			SideEffects:  &noSideEffect,
   670  			MatchConditions: []admissionregistration.MatchCondition{{
   671  				Expression: "true",
   672  			}, {
   673  				Expression: "true",
   674  			}},
   675  		}, {
   676  			Name:         "webhook.k8s.io",
   677  			ClientConfig: validClientConfig,
   678  			SideEffects:  &noSideEffect,
   679  			MatchConditions: []admissionregistration.MatchCondition{{
   680  				Name:       "",
   681  				Expression: "true",
   682  			}},
   683  		},
   684  		}, true),
   685  		expectedError: `webhooks[0].matchConditions[0].name: Required value, webhooks[0].matchConditions[1].name: Required value, webhooks[1].matchConditions[0].name: Required value`,
   686  	}, {
   687  		name: "single match condition must have a qualified name",
   688  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   689  			Name:         "webhook.k8s.io",
   690  			ClientConfig: validClientConfig,
   691  			SideEffects:  &noSideEffect,
   692  			MatchConditions: []admissionregistration.MatchCondition{{
   693  				Name:       "-hello",
   694  				Expression: "true",
   695  			}},
   696  		},
   697  		}, true),
   698  		expectedError: `webhooks[0].matchConditions[0].name: Invalid value: "-hello": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`,
   699  	}, {
   700  		name: "all match conditions must have qualified names",
   701  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   702  			Name:         "webhook.k8s.io",
   703  			ClientConfig: validClientConfig,
   704  			SideEffects:  &noSideEffect,
   705  			MatchConditions: []admissionregistration.MatchCondition{{
   706  				Name:       ".io",
   707  				Expression: "true",
   708  			}, {
   709  				Name:       "thing.test.com",
   710  				Expression: "true",
   711  			}},
   712  		}, {
   713  			Name:         "webhook2.k8s.io",
   714  			ClientConfig: validClientConfig,
   715  			SideEffects:  &noSideEffect,
   716  			MatchConditions: []admissionregistration.MatchCondition{{
   717  				Name:       "some name",
   718  				Expression: "true",
   719  			}},
   720  		},
   721  		}, true),
   722  		expectedError: `[webhooks[0].matchConditions[0].name: Invalid value: ".io": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]'), webhooks[1].matchConditions[0].name: Invalid value: "some name": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')]`,
   723  	}, {
   724  		name: "expression is required",
   725  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   726  			Name:         "webhook.k8s.io",
   727  			ClientConfig: validClientConfig,
   728  			SideEffects:  &noSideEffect,
   729  			MatchConditions: []admissionregistration.MatchCondition{{
   730  				Name: "webhook.k8s.io",
   731  			}},
   732  		},
   733  		}, true),
   734  		expectedError: `webhooks[0].matchConditions[0].expression: Required value`,
   735  	}, {
   736  		name: "expression is required to have some value",
   737  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   738  			Name:         "webhook.k8s.io",
   739  			ClientConfig: validClientConfig,
   740  			SideEffects:  &noSideEffect,
   741  			MatchConditions: []admissionregistration.MatchCondition{{
   742  				Name:       "webhook.k8s.io",
   743  				Expression: "",
   744  			}},
   745  		},
   746  		}, true),
   747  		expectedError: `webhooks[0].matchConditions[0].expression: Required value`,
   748  	}, {
   749  		name: "invalid expression",
   750  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   751  			Name:         "webhook.k8s.io",
   752  			ClientConfig: validClientConfig,
   753  			SideEffects:  &noSideEffect,
   754  			MatchConditions: []admissionregistration.MatchCondition{{
   755  				Name:       "webhook.k8s.io",
   756  				Expression: "object.x in [1, 2, ",
   757  			}},
   758  		},
   759  		}, true),
   760  		expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "object.x in [1, 2,": compilation failed: ERROR: <input>:1:19: Syntax error: missing ']' at '<EOF>'`,
   761  	}, {
   762  		name: "unique names same hook",
   763  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   764  			Name:         "webhook.k8s.io",
   765  			ClientConfig: validClientConfig,
   766  			SideEffects:  &noSideEffect,
   767  			MatchConditions: []admissionregistration.MatchCondition{{
   768  				Name:       "webhook.k8s.io",
   769  				Expression: "true",
   770  			}, {
   771  				Name:       "webhook.k8s.io",
   772  				Expression: "true",
   773  			}},
   774  		},
   775  		}, true),
   776  		expectedError: `matchConditions[1].name: Duplicate value: "webhook.k8s.io"`,
   777  	}, {
   778  		name: "repeat names allowed across different hooks",
   779  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   780  			Name:         "webhook.k8s.io",
   781  			ClientConfig: validClientConfig,
   782  			SideEffects:  &noSideEffect,
   783  			MatchConditions: []admissionregistration.MatchCondition{{
   784  				Name:       "webhook.k8s.io",
   785  				Expression: "true",
   786  			}},
   787  		}, {
   788  			Name:         "webhook2.k8s.io",
   789  			ClientConfig: validClientConfig,
   790  			SideEffects:  &noSideEffect,
   791  			MatchConditions: []admissionregistration.MatchCondition{{
   792  				Name:       "webhook.k8s.io",
   793  				Expression: "true",
   794  			}},
   795  		},
   796  		}, true),
   797  		expectedError: ``,
   798  	}, {
   799  		name: "must evaluate to bool",
   800  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   801  			Name:         "webhook.k8s.io",
   802  			ClientConfig: validClientConfig,
   803  			SideEffects:  &noSideEffect,
   804  			MatchConditions: []admissionregistration.MatchCondition{{
   805  				Name:       "webhook.k8s.io",
   806  				Expression: "6",
   807  			}},
   808  		},
   809  		}, true),
   810  		expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "6": must evaluate to bool`,
   811  	}, {
   812  		name: "max of 64 match conditions",
   813  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   814  			Name:            "webhook.k8s.io",
   815  			ClientConfig:    validClientConfig,
   816  			SideEffects:     &noSideEffect,
   817  			MatchConditions: get65MatchConditions(),
   818  		},
   819  		}, true),
   820  		expectedError: `webhooks[0].matchConditions: Too many: 65: must have at most 64 items`,
   821  	}}
   822  	for _, test := range tests {
   823  		t.Run(test.name, func(t *testing.T) {
   824  			errs := ValidateValidatingWebhookConfiguration(test.config)
   825  			err := errs.ToAggregate()
   826  			if err != nil {
   827  				if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
   828  					t.Errorf("expected to contain:\n  %s\ngot:\n  %s", e, a)
   829  				}
   830  			} else {
   831  				if test.expectedError != "" {
   832  					t.Errorf("unexpected no error, expected to contain:\n  %s", test.expectedError)
   833  				}
   834  			}
   835  		})
   836  
   837  	}
   838  }
   839  
   840  func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) {
   841  	noSideEffect := admissionregistration.SideEffectClassNone
   842  	unknownSideEffect := admissionregistration.SideEffectClassUnknown
   843  	validClientConfig := admissionregistration.WebhookClientConfig{
   844  		URL: strPtr("https://example.com"),
   845  	}
   846  	tests := []struct {
   847  		name          string
   848  		oldconfig     *admissionregistration.ValidatingWebhookConfiguration
   849  		config        *admissionregistration.ValidatingWebhookConfiguration
   850  		expectedError string
   851  	}{{
   852  		name: "should pass on valid new AdmissionReviewVersion",
   853  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   854  			Name:                    "webhook.k8s.io",
   855  			ClientConfig:            validClientConfig,
   856  			SideEffects:             &unknownSideEffect,
   857  			AdmissionReviewVersions: []string{"v1beta1"},
   858  		},
   859  		}, true),
   860  		oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   861  			Name:         "webhook.k8s.io",
   862  			ClientConfig: validClientConfig,
   863  			SideEffects:  &unknownSideEffect,
   864  		},
   865  		}, true),
   866  		expectedError: ``,
   867  	}, {
   868  		name: "should pass on invalid AdmissionReviewVersion with invalid previous versions",
   869  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   870  			Name:                    "webhook.k8s.io",
   871  			ClientConfig:            validClientConfig,
   872  			SideEffects:             &unknownSideEffect,
   873  			AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"},
   874  		},
   875  		}, true),
   876  		oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   877  			Name:                    "webhook.k8s.io",
   878  			ClientConfig:            validClientConfig,
   879  			SideEffects:             &unknownSideEffect,
   880  			AdmissionReviewVersions: []string{"invalid-v0"},
   881  		},
   882  		}, true),
   883  		expectedError: ``,
   884  	}, {
   885  		name: "should fail on invalid AdmissionReviewVersion with valid previous versions",
   886  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   887  			Name:                    "webhook.k8s.io",
   888  			ClientConfig:            validClientConfig,
   889  			SideEffects:             &unknownSideEffect,
   890  			AdmissionReviewVersions: []string{"invalid-v1"},
   891  		},
   892  		}, true),
   893  		oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   894  			Name:                    "webhook.k8s.io",
   895  			ClientConfig:            validClientConfig,
   896  			SideEffects:             &unknownSideEffect,
   897  			AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"},
   898  		},
   899  		}, true),
   900  		expectedError: `Invalid value: []string{"invalid-v1"}`,
   901  	}, {
   902  		name: "should fail on invalid AdmissionReviewVersion with missing previous versions",
   903  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   904  			Name:                    "webhook.k8s.io",
   905  			ClientConfig:            validClientConfig,
   906  			SideEffects:             &unknownSideEffect,
   907  			AdmissionReviewVersions: []string{"invalid-v1"},
   908  		},
   909  		}, true),
   910  		oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   911  			Name:         "webhook.k8s.io",
   912  			ClientConfig: validClientConfig,
   913  			SideEffects:  &unknownSideEffect,
   914  		},
   915  		}, false),
   916  		expectedError: `Invalid value: []string{"invalid-v1"}`,
   917  	}, {
   918  		name: "Webhooks must have unique names when old config has unique names",
   919  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   920  			Name:         "webhook.k8s.io",
   921  			ClientConfig: validClientConfig,
   922  			SideEffects:  &unknownSideEffect,
   923  		}, {
   924  			Name:         "webhook.k8s.io",
   925  			ClientConfig: validClientConfig,
   926  			SideEffects:  &unknownSideEffect,
   927  		},
   928  		}, true),
   929  		oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   930  			Name:         "webhook.k8s.io",
   931  			ClientConfig: validClientConfig,
   932  			SideEffects:  &unknownSideEffect,
   933  		},
   934  		}, false),
   935  		expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`,
   936  	}, {
   937  		name: "Webhooks can have duplicate names when old config has duplicate names",
   938  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   939  			Name:         "webhook.k8s.io",
   940  			ClientConfig: validClientConfig,
   941  			SideEffects:  &unknownSideEffect,
   942  		}, {
   943  			Name:         "webhook.k8s.io",
   944  			ClientConfig: validClientConfig,
   945  			SideEffects:  &unknownSideEffect,
   946  		},
   947  		}, true),
   948  		oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   949  			Name:         "webhook.k8s.io",
   950  			ClientConfig: validClientConfig,
   951  			SideEffects:  &unknownSideEffect,
   952  		}, {
   953  			Name:         "webhook.k8s.io",
   954  			ClientConfig: validClientConfig,
   955  			SideEffects:  &unknownSideEffect,
   956  		},
   957  		}, true),
   958  		expectedError: ``,
   959  	}, {
   960  		name: "Webhooks must compile CEL expressions with StoredExpression environment if unchanged",
   961  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   962  			Name:         "webhook.k8s.io",
   963  			ClientConfig: validClientConfig,
   964  			SideEffects:  &noSideEffect,
   965  			MatchConditions: []admissionregistration.MatchCondition{{
   966  				Name:       "checkStorage",
   967  				Expression: "test() == true",
   968  			}},
   969  		},
   970  		}, true),
   971  		oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   972  			Name:         "webhook.k8s.io",
   973  			ClientConfig: validClientConfig,
   974  			SideEffects:  &noSideEffect,
   975  			MatchConditions: []admissionregistration.MatchCondition{{
   976  				Name:       "checkStorage",
   977  				Expression: "test() == true",
   978  			}}},
   979  		}, true),
   980  	}, {
   981  		name: "Webhooks must compile CEL expressions with NewExpression environment type if changed",
   982  		config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   983  			Name:         "webhook.k8s.io",
   984  			ClientConfig: validClientConfig,
   985  			SideEffects:  &noSideEffect,
   986  			MatchConditions: []admissionregistration.MatchCondition{{
   987  				Name:       "checkStorage",
   988  				Expression: "test() == true",
   989  			}}},
   990  		}, true),
   991  		oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{{
   992  			Name:         "webhook.k8s.io",
   993  			ClientConfig: validClientConfig,
   994  			SideEffects:  &noSideEffect,
   995  			MatchConditions: []admissionregistration.MatchCondition{{
   996  				Name:       "checkStorage",
   997  				Expression: "true",
   998  			}}},
   999  		}, true),
  1000  		expectedError: `undeclared reference to 'test'`,
  1001  	}}
  1002  	for _, test := range tests {
  1003  		t.Run(test.name, func(t *testing.T) {
  1004  			errs := ValidateValidatingWebhookConfigurationUpdate(test.config, test.oldconfig)
  1005  			err := errs.ToAggregate()
  1006  			if err != nil {
  1007  				if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  1008  					t.Errorf("expected to contain:\n  %s\ngot:\n  %s", e, a)
  1009  				}
  1010  			} else {
  1011  				if test.expectedError != "" {
  1012  					t.Errorf("unexpected no error, expected to contain:\n  %s", test.expectedError)
  1013  				}
  1014  			}
  1015  		})
  1016  
  1017  	}
  1018  }
  1019  
  1020  func newMutatingWebhookConfiguration(hooks []admissionregistration.MutatingWebhook, defaultAdmissionReviewVersions bool) *admissionregistration.MutatingWebhookConfiguration {
  1021  	// If the test case did not specify an AdmissionReviewVersions, default it so the test passes as
  1022  	// this field will be defaulted in production code.
  1023  	for i := range hooks {
  1024  		if defaultAdmissionReviewVersions && len(hooks[i].AdmissionReviewVersions) == 0 {
  1025  			hooks[i].AdmissionReviewVersions = []string{"v1beta1"}
  1026  		}
  1027  	}
  1028  	return &admissionregistration.MutatingWebhookConfiguration{
  1029  		ObjectMeta: metav1.ObjectMeta{
  1030  			Name: "config",
  1031  		},
  1032  		Webhooks: hooks,
  1033  	}
  1034  }
  1035  
  1036  func TestValidateMutatingWebhookConfiguration(t *testing.T) {
  1037  	noSideEffect := admissionregistration.SideEffectClassNone
  1038  	unknownSideEffect := admissionregistration.SideEffectClassUnknown
  1039  	validClientConfig := admissionregistration.WebhookClientConfig{
  1040  		URL: strPtr("https://example.com"),
  1041  	}
  1042  	tests := []struct {
  1043  		name          string
  1044  		config        *admissionregistration.MutatingWebhookConfiguration
  1045  		expectedError string
  1046  	}{{
  1047  		name: "AdmissionReviewVersions are required",
  1048  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1049  			Name:         "webhook.k8s.io",
  1050  			ClientConfig: validClientConfig,
  1051  			SideEffects:  &unknownSideEffect,
  1052  		},
  1053  		}, false),
  1054  		expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`,
  1055  	}, {
  1056  		name: "should fail on bad AdmissionReviewVersion value",
  1057  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1058  			Name:                    "webhook.k8s.io",
  1059  			ClientConfig:            validClientConfig,
  1060  			AdmissionReviewVersions: []string{"0v"},
  1061  		},
  1062  		}, true),
  1063  		expectedError: `Invalid value: "0v": a DNS-1035 label`,
  1064  	}, {
  1065  		name: "should pass on valid AdmissionReviewVersion",
  1066  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1067  			Name:                    "webhook.k8s.io",
  1068  			ClientConfig:            validClientConfig,
  1069  			SideEffects:             &noSideEffect,
  1070  			AdmissionReviewVersions: []string{"v1beta1"},
  1071  		},
  1072  		}, true),
  1073  		expectedError: ``,
  1074  	}, {
  1075  		name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion",
  1076  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1077  			Name:                    "webhook.k8s.io",
  1078  			ClientConfig:            validClientConfig,
  1079  			SideEffects:             &noSideEffect,
  1080  			AdmissionReviewVersions: []string{"v1beta1", "invalid-version"},
  1081  		},
  1082  		}, true),
  1083  		expectedError: ``,
  1084  	}, {
  1085  		name: "should fail on invalid AdmissionReviewVersion",
  1086  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1087  			Name:                    "webhook.k8s.io",
  1088  			ClientConfig:            validClientConfig,
  1089  			AdmissionReviewVersions: []string{"invalidVersion"},
  1090  		},
  1091  		}, true),
  1092  		expectedError: `Invalid value: []string{"invalidVersion"}`,
  1093  	}, {
  1094  		name: "should fail on duplicate AdmissionReviewVersion",
  1095  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1096  			Name:                    "webhook.k8s.io",
  1097  			ClientConfig:            validClientConfig,
  1098  			AdmissionReviewVersions: []string{"v1beta1", "v1beta1"},
  1099  		},
  1100  		}, true),
  1101  		expectedError: `Invalid value: "v1beta1": duplicate version`,
  1102  	}, {
  1103  		name: "all Webhooks must have a fully qualified name",
  1104  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1105  			Name:         "webhook.k8s.io",
  1106  			ClientConfig: validClientConfig,
  1107  			SideEffects:  &noSideEffect,
  1108  		}, {
  1109  			Name:         "k8s.io",
  1110  			ClientConfig: validClientConfig,
  1111  			SideEffects:  &noSideEffect,
  1112  		}, {
  1113  			Name:         "",
  1114  			ClientConfig: validClientConfig,
  1115  			SideEffects:  &noSideEffect,
  1116  		},
  1117  		}, true),
  1118  		expectedError: `webhooks[1].name: Invalid value: "k8s.io": should be a domain with at least three segments separated by dots, webhooks[2].name: Required value`,
  1119  	}, {
  1120  		name: "Webhooks must have unique names when created",
  1121  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1122  			Name:         "webhook.k8s.io",
  1123  			ClientConfig: validClientConfig,
  1124  			SideEffects:  &unknownSideEffect,
  1125  		}, {
  1126  			Name:         "webhook.k8s.io",
  1127  			ClientConfig: validClientConfig,
  1128  			SideEffects:  &unknownSideEffect,
  1129  		},
  1130  		}, true),
  1131  		expectedError: `webhooks[1].name: Duplicate value: "webhook.k8s.io"`,
  1132  	}, {
  1133  		name: "Operations must not be empty or nil",
  1134  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1135  			Name: "webhook.k8s.io",
  1136  			Rules: []admissionregistration.RuleWithOperations{{
  1137  				Operations: []admissionregistration.OperationType{},
  1138  				Rule: admissionregistration.Rule{
  1139  					APIGroups:   []string{"a"},
  1140  					APIVersions: []string{"a"},
  1141  					Resources:   []string{"a"},
  1142  				},
  1143  			}, {
  1144  				Operations: nil,
  1145  				Rule: admissionregistration.Rule{
  1146  					APIGroups:   []string{"a"},
  1147  					APIVersions: []string{"a"},
  1148  					Resources:   []string{"a"},
  1149  				},
  1150  			}},
  1151  		},
  1152  		}, true),
  1153  		expectedError: `webhooks[0].rules[0].operations: Required value, webhooks[0].rules[1].operations: Required value`,
  1154  	}, {
  1155  		name: "\"\" is NOT a valid operation",
  1156  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1157  			Name: "webhook.k8s.io",
  1158  			Rules: []admissionregistration.RuleWithOperations{{
  1159  				Operations: []admissionregistration.OperationType{"CREATE", ""},
  1160  				Rule: admissionregistration.Rule{
  1161  					APIGroups:   []string{"a"},
  1162  					APIVersions: []string{"a"},
  1163  					Resources:   []string{"a"},
  1164  				},
  1165  			}},
  1166  		},
  1167  		}, true),
  1168  		expectedError: `Unsupported value: ""`,
  1169  	}, {
  1170  		name: "operation must be either create/update/delete/connect",
  1171  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1172  			Name: "webhook.k8s.io",
  1173  			Rules: []admissionregistration.RuleWithOperations{{
  1174  				Operations: []admissionregistration.OperationType{"PATCH"},
  1175  				Rule: admissionregistration.Rule{
  1176  					APIGroups:   []string{"a"},
  1177  					APIVersions: []string{"a"},
  1178  					Resources:   []string{"a"},
  1179  				},
  1180  			}},
  1181  		},
  1182  		}, true),
  1183  		expectedError: `Unsupported value: "PATCH"`,
  1184  	}, {
  1185  		name: "wildcard operation cannot be mixed with other strings",
  1186  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1187  			Name: "webhook.k8s.io",
  1188  			Rules: []admissionregistration.RuleWithOperations{{
  1189  				Operations: []admissionregistration.OperationType{"CREATE", "*"},
  1190  				Rule: admissionregistration.Rule{
  1191  					APIGroups:   []string{"a"},
  1192  					APIVersions: []string{"a"},
  1193  					Resources:   []string{"a"},
  1194  				},
  1195  			}},
  1196  		},
  1197  		}, true),
  1198  		expectedError: `if '*' is present, must not specify other operations`,
  1199  	}, {
  1200  		name: `resource "*" can co-exist with resources that have subresources`,
  1201  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1202  			Name:         "webhook.k8s.io",
  1203  			ClientConfig: validClientConfig,
  1204  			SideEffects:  &noSideEffect,
  1205  			Rules: []admissionregistration.RuleWithOperations{{
  1206  				Operations: []admissionregistration.OperationType{"CREATE"},
  1207  				Rule: admissionregistration.Rule{
  1208  					APIGroups:   []string{"a"},
  1209  					APIVersions: []string{"a"},
  1210  					Resources:   []string{"*", "a/b", "a/*", "*/b"},
  1211  				},
  1212  			}},
  1213  		},
  1214  		}, true),
  1215  	}, {
  1216  		name: `resource "*" cannot mix with resources that don't have subresources`,
  1217  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1218  			Name:         "webhook.k8s.io",
  1219  			ClientConfig: validClientConfig,
  1220  			SideEffects:  &unknownSideEffect,
  1221  			Rules: []admissionregistration.RuleWithOperations{{
  1222  				Operations: []admissionregistration.OperationType{"CREATE"},
  1223  				Rule: admissionregistration.Rule{
  1224  					APIGroups:   []string{"a"},
  1225  					APIVersions: []string{"a"},
  1226  					Resources:   []string{"*", "a"},
  1227  				},
  1228  			}},
  1229  		},
  1230  		}, true),
  1231  		expectedError: `if '*' is present, must not specify other resources without subresources`,
  1232  	}, {
  1233  		name: "resource a/* cannot mix with a/x",
  1234  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1235  			Name:         "webhook.k8s.io",
  1236  			ClientConfig: validClientConfig,
  1237  			SideEffects:  &unknownSideEffect,
  1238  			Rules: []admissionregistration.RuleWithOperations{{
  1239  				Operations: []admissionregistration.OperationType{"CREATE"},
  1240  				Rule: admissionregistration.Rule{
  1241  					APIGroups:   []string{"a"},
  1242  					APIVersions: []string{"a"},
  1243  					Resources:   []string{"a/*", "a/x"},
  1244  				},
  1245  			}},
  1246  		},
  1247  		}, true),
  1248  		expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`,
  1249  	}, {
  1250  		name: "resource a/* can mix with a",
  1251  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1252  			Name:         "webhook.k8s.io",
  1253  			ClientConfig: validClientConfig,
  1254  			SideEffects:  &noSideEffect,
  1255  			Rules: []admissionregistration.RuleWithOperations{{
  1256  				Operations: []admissionregistration.OperationType{"CREATE"},
  1257  				Rule: admissionregistration.Rule{
  1258  					APIGroups:   []string{"a"},
  1259  					APIVersions: []string{"a"},
  1260  					Resources:   []string{"a/*", "a"},
  1261  				},
  1262  			}},
  1263  		},
  1264  		}, true),
  1265  	}, {
  1266  		name: "resource */a cannot mix with x/a",
  1267  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1268  			Name:         "webhook.k8s.io",
  1269  			ClientConfig: validClientConfig,
  1270  			SideEffects:  &unknownSideEffect,
  1271  			Rules: []admissionregistration.RuleWithOperations{{
  1272  				Operations: []admissionregistration.OperationType{"CREATE"},
  1273  				Rule: admissionregistration.Rule{
  1274  					APIGroups:   []string{"a"},
  1275  					APIVersions: []string{"a"},
  1276  					Resources:   []string{"*/a", "x/a"},
  1277  				},
  1278  			}},
  1279  		},
  1280  		}, true),
  1281  		expectedError: `webhooks[0].rules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`,
  1282  	}, {
  1283  		name: "resource */* cannot mix with other resources",
  1284  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1285  			Name:         "webhook.k8s.io",
  1286  			ClientConfig: validClientConfig,
  1287  			SideEffects:  &unknownSideEffect,
  1288  			Rules: []admissionregistration.RuleWithOperations{{
  1289  				Operations: []admissionregistration.OperationType{"CREATE"},
  1290  				Rule: admissionregistration.Rule{
  1291  					APIGroups:   []string{"a"},
  1292  					APIVersions: []string{"a"},
  1293  					Resources:   []string{"*/*", "a"},
  1294  				},
  1295  			}},
  1296  		},
  1297  		}, true),
  1298  		expectedError: `webhooks[0].rules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
  1299  	}, {
  1300  		name: "FailurePolicy can only be \"Ignore\" or \"Fail\"",
  1301  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1302  			Name:         "webhook.k8s.io",
  1303  			ClientConfig: validClientConfig,
  1304  			SideEffects:  &unknownSideEffect,
  1305  			FailurePolicy: func() *admissionregistration.FailurePolicyType {
  1306  				r := admissionregistration.FailurePolicyType("other")
  1307  				return &r
  1308  			}(),
  1309  		},
  1310  		}, true),
  1311  		expectedError: `webhooks[0].failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
  1312  	}, {
  1313  		name: "AdmissionReviewVersions are required",
  1314  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1315  			Name:         "webhook.k8s.io",
  1316  			ClientConfig: validClientConfig,
  1317  			SideEffects:  &unknownSideEffect,
  1318  		},
  1319  		}, false),
  1320  		expectedError: `webhooks[0].admissionReviewVersions: Required value: must specify one of v1, v1beta1`,
  1321  	}, {
  1322  		name: "SideEffects are required",
  1323  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1324  			Name:         "webhook.k8s.io",
  1325  			ClientConfig: validClientConfig,
  1326  			SideEffects:  nil,
  1327  		},
  1328  		}, true),
  1329  		expectedError: `webhooks[0].sideEffects: Required value: must specify one of None, NoneOnDryRun`,
  1330  	}, {
  1331  		name: "SideEffects can only be \"None\" or \"NoneOnDryRun\" when created",
  1332  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1333  			Name:         "webhook.k8s.io",
  1334  			ClientConfig: validClientConfig,
  1335  			SideEffects: func() *admissionregistration.SideEffectClass {
  1336  				r := admissionregistration.SideEffectClass("other")
  1337  				return &r
  1338  			}(),
  1339  		},
  1340  		}, true),
  1341  		expectedError: `webhooks[0].sideEffects: Unsupported value: "other": supported values: "None", "NoneOnDryRun"`,
  1342  	}, {
  1343  		name: "both service and URL missing",
  1344  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1345  			Name:         "webhook.k8s.io",
  1346  			ClientConfig: admissionregistration.WebhookClientConfig{},
  1347  		},
  1348  		}, true),
  1349  		expectedError: `exactly one of`,
  1350  	}, {
  1351  		name: "both service and URL provided",
  1352  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1353  			Name: "webhook.k8s.io",
  1354  			ClientConfig: admissionregistration.WebhookClientConfig{
  1355  				Service: &admissionregistration.ServiceReference{
  1356  					Namespace: "ns",
  1357  					Name:      "n",
  1358  					Port:      443,
  1359  				},
  1360  				URL: strPtr("example.com/k8s/webhook"),
  1361  			},
  1362  		},
  1363  		}, true),
  1364  		expectedError: `[0].clientConfig: Required value: exactly one of url or service is required`,
  1365  	}, {
  1366  		name: "blank URL",
  1367  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1368  			Name: "webhook.k8s.io",
  1369  			ClientConfig: admissionregistration.WebhookClientConfig{
  1370  				URL: strPtr(""),
  1371  			},
  1372  		},
  1373  		}, true),
  1374  		expectedError: `[0].clientConfig.url: Invalid value: "": host must be specified`,
  1375  	}, {
  1376  		name: "wrong scheme",
  1377  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1378  			Name: "webhook.k8s.io",
  1379  			ClientConfig: admissionregistration.WebhookClientConfig{
  1380  				URL: strPtr("http://example.com"),
  1381  			},
  1382  		},
  1383  		}, true),
  1384  		expectedError: `https`,
  1385  	}, {
  1386  		name: "missing host",
  1387  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1388  			Name: "webhook.k8s.io",
  1389  			ClientConfig: admissionregistration.WebhookClientConfig{
  1390  				URL: strPtr("https:///fancy/webhook"),
  1391  			},
  1392  		},
  1393  		}, true),
  1394  		expectedError: `host must be specified`,
  1395  	}, {
  1396  		name: "fragment",
  1397  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1398  			Name: "webhook.k8s.io",
  1399  			ClientConfig: admissionregistration.WebhookClientConfig{
  1400  				URL: strPtr("https://example.com/#bookmark"),
  1401  			},
  1402  		},
  1403  		}, true),
  1404  		expectedError: `"bookmark": fragments are not permitted`,
  1405  	}, {
  1406  		name: "query",
  1407  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1408  			Name: "webhook.k8s.io",
  1409  			ClientConfig: admissionregistration.WebhookClientConfig{
  1410  				URL: strPtr("https://example.com?arg=value"),
  1411  			},
  1412  		},
  1413  		}, true),
  1414  		expectedError: `"arg=value": query parameters are not permitted`,
  1415  	}, {
  1416  		name: "user",
  1417  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1418  			Name: "webhook.k8s.io",
  1419  			ClientConfig: admissionregistration.WebhookClientConfig{
  1420  				URL: strPtr("https://harry.potter@example.com/"),
  1421  			},
  1422  		},
  1423  		}, true),
  1424  		expectedError: `"harry.potter": user information is not permitted`,
  1425  	}, {
  1426  		name: "just totally wrong",
  1427  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1428  			Name: "webhook.k8s.io",
  1429  			ClientConfig: admissionregistration.WebhookClientConfig{
  1430  				URL: strPtr("arg#backwards=thisis?html.index/port:host//:https"),
  1431  			},
  1432  		},
  1433  		}, true),
  1434  		expectedError: `host must be specified`,
  1435  	}, {
  1436  		name: "path must start with slash",
  1437  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1438  			Name: "webhook.k8s.io",
  1439  			ClientConfig: admissionregistration.WebhookClientConfig{
  1440  				Service: &admissionregistration.ServiceReference{
  1441  					Namespace: "ns",
  1442  					Name:      "n",
  1443  					Path:      strPtr("foo/"),
  1444  					Port:      443,
  1445  				},
  1446  			},
  1447  		},
  1448  		}, true),
  1449  		expectedError: `clientConfig.service.path: Invalid value: "foo/": must start with a '/'`,
  1450  	}, {
  1451  		name: "path accepts slash",
  1452  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1453  			Name: "webhook.k8s.io",
  1454  			ClientConfig: admissionregistration.WebhookClientConfig{
  1455  				Service: &admissionregistration.ServiceReference{
  1456  					Namespace: "ns",
  1457  					Name:      "n",
  1458  					Path:      strPtr("/"),
  1459  					Port:      443,
  1460  				},
  1461  			},
  1462  			SideEffects: &noSideEffect,
  1463  		},
  1464  		}, true),
  1465  		expectedError: ``,
  1466  	}, {
  1467  		name: "path accepts no trailing slash",
  1468  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1469  			Name: "webhook.k8s.io",
  1470  			ClientConfig: admissionregistration.WebhookClientConfig{
  1471  				Service: &admissionregistration.ServiceReference{
  1472  					Namespace: "ns",
  1473  					Name:      "n",
  1474  					Path:      strPtr("/foo"),
  1475  					Port:      443,
  1476  				},
  1477  			},
  1478  			SideEffects: &noSideEffect,
  1479  		},
  1480  		}, true),
  1481  		expectedError: ``,
  1482  	}, {
  1483  		name: "path fails //",
  1484  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1485  			Name: "webhook.k8s.io",
  1486  			ClientConfig: admissionregistration.WebhookClientConfig{
  1487  				Service: &admissionregistration.ServiceReference{
  1488  					Namespace: "ns",
  1489  					Name:      "n",
  1490  					Path:      strPtr("//"),
  1491  					Port:      443,
  1492  				},
  1493  			},
  1494  			SideEffects: &noSideEffect,
  1495  		},
  1496  		}, true),
  1497  		expectedError: `clientConfig.service.path: Invalid value: "//": segment[0] may not be empty`,
  1498  	}, {
  1499  		name: "path no empty step",
  1500  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1501  			Name: "webhook.k8s.io",
  1502  			ClientConfig: admissionregistration.WebhookClientConfig{
  1503  				Service: &admissionregistration.ServiceReference{
  1504  					Namespace: "ns",
  1505  					Name:      "n",
  1506  					Path:      strPtr("/foo//bar/"),
  1507  					Port:      443,
  1508  				},
  1509  			},
  1510  			SideEffects: &unknownSideEffect,
  1511  		},
  1512  		}, true),
  1513  		expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`,
  1514  	}, {
  1515  		name: "path no empty step 2",
  1516  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1517  			Name: "webhook.k8s.io",
  1518  			ClientConfig: admissionregistration.WebhookClientConfig{
  1519  				Service: &admissionregistration.ServiceReference{
  1520  					Namespace: "ns",
  1521  					Name:      "n",
  1522  					Path:      strPtr("/foo/bar//"),
  1523  					Port:      443,
  1524  				},
  1525  			},
  1526  			SideEffects: &unknownSideEffect,
  1527  		},
  1528  		}, true),
  1529  		expectedError: `clientConfig.service.path: Invalid value: "/foo/bar//": segment[2] may not be empty`,
  1530  	}, {
  1531  		name: "path no non-subdomain",
  1532  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1533  			Name: "webhook.k8s.io",
  1534  			ClientConfig: admissionregistration.WebhookClientConfig{
  1535  				Service: &admissionregistration.ServiceReference{
  1536  					Namespace: "ns",
  1537  					Name:      "n",
  1538  					Path:      strPtr("/apis/foo.bar/v1alpha1/--bad"),
  1539  					Port:      443,
  1540  				},
  1541  			},
  1542  			SideEffects: &unknownSideEffect,
  1543  		},
  1544  		}, true),
  1545  		expectedError: `clientConfig.service.path: Invalid value: "/apis/foo.bar/v1alpha1/--bad": segment[3]: a lowercase RFC 1123 subdomain`,
  1546  	}, {
  1547  		name: "invalid port 0",
  1548  		config: newMutatingWebhookConfiguration(
  1549  			[]admissionregistration.MutatingWebhook{{
  1550  				Name: "webhook.k8s.io",
  1551  				ClientConfig: admissionregistration.WebhookClientConfig{
  1552  					Service: &admissionregistration.ServiceReference{
  1553  						Namespace: "ns",
  1554  						Name:      "n",
  1555  						Path:      strPtr("https://apis/foo.bar"),
  1556  						Port:      0,
  1557  					},
  1558  				},
  1559  				SideEffects: &unknownSideEffect,
  1560  			},
  1561  			}, true),
  1562  		expectedError: `Invalid value: 0: port is not valid: must be between 1 and 65535, inclusive`,
  1563  	}, {
  1564  		name: "invalid port >65535",
  1565  		config: newMutatingWebhookConfiguration(
  1566  			[]admissionregistration.MutatingWebhook{{
  1567  				Name: "webhook.k8s.io",
  1568  				ClientConfig: admissionregistration.WebhookClientConfig{
  1569  					Service: &admissionregistration.ServiceReference{
  1570  						Namespace: "ns",
  1571  						Name:      "n",
  1572  						Path:      strPtr("https://apis/foo.bar"),
  1573  						Port:      65536,
  1574  					},
  1575  				},
  1576  				SideEffects: &unknownSideEffect,
  1577  			},
  1578  			}, true),
  1579  		expectedError: `Invalid value: 65536: port is not valid: must be between 1 and 65535, inclusive`,
  1580  	}, {
  1581  		name: "timeout seconds cannot be greater than 30",
  1582  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1583  			Name:           "webhook.k8s.io",
  1584  			ClientConfig:   validClientConfig,
  1585  			SideEffects:    &unknownSideEffect,
  1586  			TimeoutSeconds: int32Ptr(31),
  1587  		},
  1588  		}, true),
  1589  		expectedError: `webhooks[0].timeoutSeconds: Invalid value: 31: the timeout value must be between 1 and 30 seconds`,
  1590  	}, {
  1591  		name: "timeout seconds cannot be smaller than 1",
  1592  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1593  			Name:           "webhook.k8s.io",
  1594  			ClientConfig:   validClientConfig,
  1595  			SideEffects:    &unknownSideEffect,
  1596  			TimeoutSeconds: int32Ptr(0),
  1597  		},
  1598  		}, true),
  1599  		expectedError: `webhooks[0].timeoutSeconds: Invalid value: 0: the timeout value must be between 1 and 30 seconds`,
  1600  	}, {
  1601  		name: "timeout seconds must be positive",
  1602  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1603  			Name:           "webhook.k8s.io",
  1604  			ClientConfig:   validClientConfig,
  1605  			SideEffects:    &unknownSideEffect,
  1606  			TimeoutSeconds: int32Ptr(-1),
  1607  		},
  1608  		}, true),
  1609  		expectedError: `webhooks[0].timeoutSeconds: Invalid value: -1: the timeout value must be between 1 and 30 seconds`,
  1610  	}, {
  1611  		name: "valid timeout seconds",
  1612  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1613  			Name:           "webhook.k8s.io",
  1614  			ClientConfig:   validClientConfig,
  1615  			SideEffects:    &noSideEffect,
  1616  			TimeoutSeconds: int32Ptr(1),
  1617  		}, {
  1618  			Name:           "webhook2.k8s.io",
  1619  			ClientConfig:   validClientConfig,
  1620  			SideEffects:    &noSideEffect,
  1621  			TimeoutSeconds: int32Ptr(15),
  1622  		}, {
  1623  			Name:           "webhook3.k8s.io",
  1624  			ClientConfig:   validClientConfig,
  1625  			SideEffects:    &noSideEffect,
  1626  			TimeoutSeconds: int32Ptr(30),
  1627  		},
  1628  		}, true),
  1629  	}, {
  1630  		name: "single match condition must have a name",
  1631  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1632  			Name:         "webhook.k8s.io",
  1633  			ClientConfig: validClientConfig,
  1634  			SideEffects:  &noSideEffect,
  1635  			MatchConditions: []admissionregistration.MatchCondition{{
  1636  				Expression: "true",
  1637  			}},
  1638  		},
  1639  		}, true),
  1640  		expectedError: `webhooks[0].matchConditions[0].name: Required value`,
  1641  	}, {
  1642  		name: "all match conditions must have a name",
  1643  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1644  			Name:         "webhook.k8s.io",
  1645  			ClientConfig: validClientConfig,
  1646  			SideEffects:  &noSideEffect,
  1647  			MatchConditions: []admissionregistration.MatchCondition{{
  1648  				Expression: "true",
  1649  			}, {
  1650  				Expression: "true",
  1651  			}},
  1652  		}, {
  1653  			Name:         "webhook.k8s.io",
  1654  			ClientConfig: validClientConfig,
  1655  			SideEffects:  &noSideEffect,
  1656  			MatchConditions: []admissionregistration.MatchCondition{{
  1657  				Name:       "",
  1658  				Expression: "true",
  1659  			}},
  1660  		},
  1661  		}, true),
  1662  		expectedError: `webhooks[0].matchConditions[0].name: Required value, webhooks[0].matchConditions[1].name: Required value, webhooks[1].matchConditions[0].name: Required value`,
  1663  	}, {
  1664  		name: "single match condition must have a qualified name",
  1665  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1666  			Name:         "webhook.k8s.io",
  1667  			ClientConfig: validClientConfig,
  1668  			SideEffects:  &noSideEffect,
  1669  			MatchConditions: []admissionregistration.MatchCondition{{
  1670  				Name:       "-hello",
  1671  				Expression: "true",
  1672  			}},
  1673  		},
  1674  		}, true),
  1675  		expectedError: `webhooks[0].matchConditions[0].name: Invalid value: "-hello": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')`,
  1676  	}, {
  1677  		name: "all match conditions must have qualified names",
  1678  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1679  			Name:         "webhook.k8s.io",
  1680  			ClientConfig: validClientConfig,
  1681  			SideEffects:  &noSideEffect,
  1682  			MatchConditions: []admissionregistration.MatchCondition{{
  1683  				Name:       ".io",
  1684  				Expression: "true",
  1685  			}, {
  1686  				Name:       "thing.test.com",
  1687  				Expression: "true",
  1688  			}},
  1689  		}, {
  1690  			Name:         "webhook2.k8s.io",
  1691  			ClientConfig: validClientConfig,
  1692  			SideEffects:  &noSideEffect,
  1693  			MatchConditions: []admissionregistration.MatchCondition{{
  1694  				Name:       "some name",
  1695  				Expression: "true",
  1696  			}},
  1697  		},
  1698  		}, true),
  1699  		expectedError: `[webhooks[0].matchConditions[0].name: Invalid value: ".io": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]'), webhooks[1].matchConditions[0].name: Invalid value: "some name": name part must consist of alphanumeric characters, '-', '_' or '.', and must start and end with an alphanumeric character (e.g. 'MyName',  or 'my.name',  or '123-abc', regex used for validation is '([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9]')]`,
  1700  	}, {
  1701  		name: "expression is required",
  1702  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1703  			Name:         "webhook.k8s.io",
  1704  			ClientConfig: validClientConfig,
  1705  			SideEffects:  &noSideEffect,
  1706  			MatchConditions: []admissionregistration.MatchCondition{{
  1707  				Name: "webhook.k8s.io",
  1708  			}},
  1709  		},
  1710  		}, true),
  1711  		expectedError: `webhooks[0].matchConditions[0].expression: Required value`,
  1712  	}, {
  1713  		name: "expression is required to have some value",
  1714  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1715  			Name:         "webhook.k8s.io",
  1716  			ClientConfig: validClientConfig,
  1717  			SideEffects:  &noSideEffect,
  1718  			MatchConditions: []admissionregistration.MatchCondition{{
  1719  				Name:       "webhook.k8s.io",
  1720  				Expression: "",
  1721  			}},
  1722  		},
  1723  		}, true),
  1724  		expectedError: `webhooks[0].matchConditions[0].expression: Required value`,
  1725  	}, {
  1726  		name: "invalid expression",
  1727  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1728  			Name:         "webhook.k8s.io",
  1729  			ClientConfig: validClientConfig,
  1730  			SideEffects:  &noSideEffect,
  1731  			MatchConditions: []admissionregistration.MatchCondition{{
  1732  				Name:       "webhook.k8s.io",
  1733  				Expression: "object.x in [1, 2, ",
  1734  			}},
  1735  		},
  1736  		}, true),
  1737  		expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "object.x in [1, 2,": compilation failed: ERROR: <input>:1:19: Syntax error: missing ']' at '<EOF>'`,
  1738  	}, {
  1739  		name: "unique names same hook",
  1740  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1741  			Name:         "webhook.k8s.io",
  1742  			ClientConfig: validClientConfig,
  1743  			SideEffects:  &noSideEffect,
  1744  			MatchConditions: []admissionregistration.MatchCondition{{
  1745  				Name:       "webhook.k8s.io",
  1746  				Expression: "true",
  1747  			}, {
  1748  				Name:       "webhook.k8s.io",
  1749  				Expression: "true",
  1750  			}},
  1751  		},
  1752  		}, true),
  1753  		expectedError: `matchConditions[1].name: Duplicate value: "webhook.k8s.io"`,
  1754  	}, {
  1755  		name: "repeat names allowed across different hooks",
  1756  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1757  			Name:         "webhook.k8s.io",
  1758  			ClientConfig: validClientConfig,
  1759  			SideEffects:  &noSideEffect,
  1760  			MatchConditions: []admissionregistration.MatchCondition{{
  1761  				Name:       "webhook.k8s.io",
  1762  				Expression: "true",
  1763  			}},
  1764  		}, {
  1765  			Name:         "webhook2.k8s.io",
  1766  			ClientConfig: validClientConfig,
  1767  			SideEffects:  &noSideEffect,
  1768  			MatchConditions: []admissionregistration.MatchCondition{{
  1769  				Name:       "webhook.k8s.io",
  1770  				Expression: "true",
  1771  			}},
  1772  		},
  1773  		}, true),
  1774  		expectedError: ``,
  1775  	}, {
  1776  		name: "must evaluate to bool",
  1777  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1778  			Name:         "webhook.k8s.io",
  1779  			ClientConfig: validClientConfig,
  1780  			SideEffects:  &noSideEffect,
  1781  			MatchConditions: []admissionregistration.MatchCondition{{
  1782  				Name:       "webhook.k8s.io",
  1783  				Expression: "6",
  1784  			}},
  1785  		},
  1786  		}, true),
  1787  		expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "6": must evaluate to bool`,
  1788  	}, {
  1789  		name: "max of 64 match conditions",
  1790  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1791  			Name:            "webhook.k8s.io",
  1792  			ClientConfig:    validClientConfig,
  1793  			SideEffects:     &noSideEffect,
  1794  			MatchConditions: get65MatchConditions(),
  1795  		},
  1796  		}, true),
  1797  		expectedError: `webhooks[0].matchConditions: Too many: 65: must have at most 64 items`,
  1798  	}}
  1799  	for _, test := range tests {
  1800  		t.Run(test.name, func(t *testing.T) {
  1801  			errs := ValidateMutatingWebhookConfiguration(test.config)
  1802  			err := errs.ToAggregate()
  1803  			if err != nil {
  1804  				if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  1805  					t.Errorf("expected to contain:\n  %s\ngot:\n  %s", e, a)
  1806  				}
  1807  			} else {
  1808  				if test.expectedError != "" {
  1809  					t.Errorf("unexpected no error, expected to contain:\n  %s", test.expectedError)
  1810  				}
  1811  			}
  1812  		})
  1813  
  1814  	}
  1815  }
  1816  
  1817  func TestValidateMutatingWebhookConfigurationUpdate(t *testing.T) {
  1818  	unknownSideEffect := admissionregistration.SideEffectClassUnknown
  1819  	noSideEffect := admissionregistration.SideEffectClassNone
  1820  	validClientConfig := admissionregistration.WebhookClientConfig{
  1821  		URL: strPtr("https://example.com"),
  1822  	}
  1823  	tests := []struct {
  1824  		name          string
  1825  		oldconfig     *admissionregistration.MutatingWebhookConfiguration
  1826  		config        *admissionregistration.MutatingWebhookConfiguration
  1827  		expectedError string
  1828  	}{{
  1829  		name: "should pass on valid new AdmissionReviewVersion (v1beta1)",
  1830  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1831  			Name:                    "webhook.k8s.io",
  1832  			ClientConfig:            validClientConfig,
  1833  			SideEffects:             &unknownSideEffect,
  1834  			AdmissionReviewVersions: []string{"v1beta1"},
  1835  		},
  1836  		}, true),
  1837  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1838  			Name:         "webhook.k8s.io",
  1839  			ClientConfig: validClientConfig,
  1840  			SideEffects:  &unknownSideEffect,
  1841  		},
  1842  		}, true),
  1843  		expectedError: ``,
  1844  	}, {
  1845  		name: "should pass on valid new AdmissionReviewVersion (v1)",
  1846  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1847  			Name:                    "webhook.k8s.io",
  1848  			ClientConfig:            validClientConfig,
  1849  			SideEffects:             &unknownSideEffect,
  1850  			AdmissionReviewVersions: []string{"v1"},
  1851  		},
  1852  		}, true),
  1853  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1854  			Name:         "webhook.k8s.io",
  1855  			ClientConfig: validClientConfig,
  1856  			SideEffects:  &unknownSideEffect,
  1857  		},
  1858  		}, true),
  1859  		expectedError: ``,
  1860  	}, {
  1861  		name: "should pass on invalid AdmissionReviewVersion with invalid previous versions",
  1862  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1863  			Name:                    "webhook.k8s.io",
  1864  			ClientConfig:            validClientConfig,
  1865  			SideEffects:             &unknownSideEffect,
  1866  			AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"},
  1867  		},
  1868  		}, true),
  1869  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1870  			Name:                    "webhook.k8s.io",
  1871  			ClientConfig:            validClientConfig,
  1872  			SideEffects:             &unknownSideEffect,
  1873  			AdmissionReviewVersions: []string{"invalid-v0"},
  1874  		},
  1875  		}, true),
  1876  		expectedError: ``,
  1877  	}, {
  1878  		name: "should fail on invalid AdmissionReviewVersion with valid previous versions",
  1879  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1880  			Name:                    "webhook.k8s.io",
  1881  			ClientConfig:            validClientConfig,
  1882  			SideEffects:             &unknownSideEffect,
  1883  			AdmissionReviewVersions: []string{"invalid-v1"},
  1884  		},
  1885  		}, true),
  1886  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1887  			Name:                    "webhook.k8s.io",
  1888  			ClientConfig:            validClientConfig,
  1889  			SideEffects:             &unknownSideEffect,
  1890  			AdmissionReviewVersions: []string{"v1beta1", "invalid-v1"},
  1891  		},
  1892  		}, true),
  1893  		expectedError: `Invalid value: []string{"invalid-v1"}`,
  1894  	}, {
  1895  		name: "should fail on invalid AdmissionReviewVersion with missing previous versions",
  1896  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1897  			Name:                    "webhook.k8s.io",
  1898  			ClientConfig:            validClientConfig,
  1899  			SideEffects:             &unknownSideEffect,
  1900  			AdmissionReviewVersions: []string{"invalid-v1"},
  1901  		},
  1902  		}, true),
  1903  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1904  			Name:         "webhook.k8s.io",
  1905  			ClientConfig: validClientConfig,
  1906  			SideEffects:  &unknownSideEffect,
  1907  		},
  1908  		}, false),
  1909  		expectedError: `Invalid value: []string{"invalid-v1"}`,
  1910  	}, {
  1911  		name: "Webhooks can have duplicate names when old config has duplicate names",
  1912  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1913  			Name:         "webhook.k8s.io",
  1914  			ClientConfig: validClientConfig,
  1915  			SideEffects:  &unknownSideEffect,
  1916  		}, {
  1917  			Name:         "webhook.k8s.io",
  1918  			ClientConfig: validClientConfig,
  1919  			SideEffects:  &unknownSideEffect,
  1920  		},
  1921  		}, true),
  1922  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1923  			Name:         "webhook.k8s.io",
  1924  			ClientConfig: validClientConfig,
  1925  			SideEffects:  &unknownSideEffect,
  1926  		}, {
  1927  			Name:         "webhook.k8s.io",
  1928  			ClientConfig: validClientConfig,
  1929  			SideEffects:  &unknownSideEffect,
  1930  		},
  1931  		}, true),
  1932  		expectedError: ``,
  1933  	}, {
  1934  		name: "Webhooks can't have side effects when old config has no side effects",
  1935  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1936  			Name:         "webhook.k8s.io",
  1937  			ClientConfig: validClientConfig,
  1938  			SideEffects:  &unknownSideEffect,
  1939  		},
  1940  		}, true),
  1941  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1942  			Name:         "webhook.k8s.io",
  1943  			ClientConfig: validClientConfig,
  1944  			SideEffects:  &noSideEffect,
  1945  		},
  1946  		}, true),
  1947  		expectedError: `Unsupported value: "Unknown": supported values: "None", "NoneOnDryRun"`,
  1948  	}, {
  1949  		name: "Webhooks can have side effects when old config has side effects",
  1950  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1951  			Name:         "webhook.k8s.io",
  1952  			ClientConfig: validClientConfig,
  1953  			SideEffects:  &unknownSideEffect,
  1954  		},
  1955  		}, true),
  1956  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1957  			Name:         "webhook.k8s.io",
  1958  			ClientConfig: validClientConfig,
  1959  			SideEffects:  &unknownSideEffect,
  1960  		},
  1961  		}, true),
  1962  		expectedError: ``,
  1963  	}, {
  1964  		name: "Webhooks must compile CEL expressions with StoredExpression environment if unchanged",
  1965  		config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1966  			Name:         "webhook.k8s.io",
  1967  			ClientConfig: validClientConfig,
  1968  			SideEffects:  &noSideEffect,
  1969  			MatchConditions: []admissionregistration.MatchCondition{{
  1970  				Name:       "checkStorage",
  1971  				Expression: "test() == true",
  1972  			}},
  1973  		},
  1974  		}, true),
  1975  		oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1976  			Name:         "webhook.k8s.io",
  1977  			ClientConfig: validClientConfig,
  1978  			SideEffects:  &noSideEffect,
  1979  			MatchConditions: []admissionregistration.MatchCondition{{
  1980  				Name:       "checkStorage",
  1981  				Expression: "test() == true",
  1982  			}},
  1983  		},
  1984  		}, true),
  1985  	},
  1986  		{
  1987  			name: "Webhooks must compile CEL expressions with NewExpression environment if changed",
  1988  			config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1989  				Name:         "webhook.k8s.io",
  1990  				ClientConfig: validClientConfig,
  1991  				SideEffects:  &noSideEffect,
  1992  				MatchConditions: []admissionregistration.MatchCondition{{
  1993  					Name:       "checkStorage",
  1994  					Expression: "test() == true",
  1995  				},
  1996  				}},
  1997  			}, true),
  1998  			oldconfig: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{{
  1999  				Name:         "webhook.k8s.io",
  2000  				ClientConfig: validClientConfig,
  2001  				SideEffects:  &noSideEffect,
  2002  				MatchConditions: []admissionregistration.MatchCondition{{
  2003  					Name:       "checkStorage",
  2004  					Expression: "true",
  2005  				},
  2006  				}},
  2007  			}, true),
  2008  			expectedError: `undeclared reference to 'test'`,
  2009  		}}
  2010  	for _, test := range tests {
  2011  		t.Run(test.name, func(t *testing.T) {
  2012  			errs := ValidateMutatingWebhookConfigurationUpdate(test.config, test.oldconfig)
  2013  			err := errs.ToAggregate()
  2014  			if err != nil {
  2015  				if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  2016  					t.Errorf("expected to contain:\n  %s\ngot:\n  %s", e, a)
  2017  				}
  2018  			} else {
  2019  				if test.expectedError != "" {
  2020  					t.Errorf("unexpected no error, expected to contain:\n  %s", test.expectedError)
  2021  				}
  2022  			}
  2023  		})
  2024  
  2025  	}
  2026  }
  2027  
  2028  func TestValidateValidatingAdmissionPolicy(t *testing.T) {
  2029  	tests := []struct {
  2030  		name          string
  2031  		config        *admissionregistration.ValidatingAdmissionPolicy
  2032  		expectedError string
  2033  	}{{
  2034  		name: "metadata.name validation",
  2035  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2036  			ObjectMeta: metav1.ObjectMeta{
  2037  				Name: "!!!!",
  2038  			},
  2039  		},
  2040  		expectedError: `metadata.name: Invalid value: "!!!!":`,
  2041  	}, {
  2042  		name: "failure policy validation",
  2043  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2044  			ObjectMeta: metav1.ObjectMeta{
  2045  				Name: "config",
  2046  			},
  2047  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2048  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  2049  					r := admissionregistration.FailurePolicyType("other")
  2050  					return &r
  2051  				}(),
  2052  				Validations: []admissionregistration.Validation{{
  2053  					Expression: "object.x < 100",
  2054  				}},
  2055  			},
  2056  		},
  2057  		expectedError: `spec.failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
  2058  	}, {
  2059  		name: "failure policy validation",
  2060  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2061  			ObjectMeta: metav1.ObjectMeta{
  2062  				Name: "config",
  2063  			},
  2064  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2065  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  2066  					r := admissionregistration.FailurePolicyType("other")
  2067  					return &r
  2068  				}(),
  2069  			},
  2070  		},
  2071  		expectedError: `spec.failurePolicy: Unsupported value: "other": supported values: "Fail", "Ignore"`,
  2072  	}, {
  2073  		name: "API version is required in ParamKind",
  2074  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2075  			ObjectMeta: metav1.ObjectMeta{
  2076  				Name: "config",
  2077  			},
  2078  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2079  				Validations: []admissionregistration.Validation{{
  2080  					Expression: "object.x < 100",
  2081  				}},
  2082  				ParamKind: &admissionregistration.ParamKind{
  2083  					Kind:       "Example",
  2084  					APIVersion: "test.example.com",
  2085  				},
  2086  			},
  2087  		},
  2088  		expectedError: `spec.paramKind.apiVersion: Invalid value: "test.example.com"`,
  2089  	}, {
  2090  		name: "API kind is required in ParamKind",
  2091  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2092  			ObjectMeta: metav1.ObjectMeta{
  2093  				Name: "config",
  2094  			},
  2095  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2096  				Validations: []admissionregistration.Validation{{
  2097  					Expression: "object.x < 100",
  2098  				}},
  2099  				ParamKind: &admissionregistration.ParamKind{
  2100  					APIVersion: "test.example.com/v1",
  2101  				},
  2102  			},
  2103  		},
  2104  		expectedError: `spec.paramKind.kind: Required value`,
  2105  	}, {
  2106  		name: "API version format in ParamKind",
  2107  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2108  			ObjectMeta: metav1.ObjectMeta{
  2109  				Name: "config",
  2110  			},
  2111  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2112  				Validations: []admissionregistration.Validation{{
  2113  					Expression: "object.x < 100",
  2114  				}},
  2115  				ParamKind: &admissionregistration.ParamKind{
  2116  					Kind:       "Example",
  2117  					APIVersion: "test.example.com/!!!",
  2118  				},
  2119  			},
  2120  		},
  2121  		expectedError: `pec.paramKind.apiVersion: Invalid value: "!!!":`,
  2122  	}, {
  2123  		name: "API group format in ParamKind",
  2124  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2125  			ObjectMeta: metav1.ObjectMeta{
  2126  				Name: "config",
  2127  			},
  2128  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2129  				Validations: []admissionregistration.Validation{{
  2130  					Expression: "object.x < 100",
  2131  				}},
  2132  				ParamKind: &admissionregistration.ParamKind{
  2133  					APIVersion: "!!!/v1",
  2134  					Kind:       "ReplicaLimit",
  2135  				},
  2136  			},
  2137  		},
  2138  		expectedError: `pec.paramKind.apiVersion: Invalid value: "!!!":`,
  2139  	}, {
  2140  		name: "Validations is required",
  2141  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2142  			ObjectMeta: metav1.ObjectMeta{
  2143  				Name: "config",
  2144  			},
  2145  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{},
  2146  		},
  2147  
  2148  		expectedError: `spec.validations: Required value: validations or auditAnnotations must contain at least one item`,
  2149  	}, {
  2150  		name: "Invalid Validations Reason",
  2151  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2152  			ObjectMeta: metav1.ObjectMeta{
  2153  				Name: "config",
  2154  			},
  2155  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2156  				Validations: []admissionregistration.Validation{{
  2157  					Expression: "object.x < 100",
  2158  					Reason: func() *metav1.StatusReason {
  2159  						r := metav1.StatusReason("other")
  2160  						return &r
  2161  					}(),
  2162  				}},
  2163  			},
  2164  		},
  2165  
  2166  		expectedError: `spec.validations[0].reason: Unsupported value: "other"`,
  2167  	}, {
  2168  		name: "MatchConstraints is required",
  2169  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2170  			ObjectMeta: metav1.ObjectMeta{
  2171  				Name: "config",
  2172  			},
  2173  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2174  				Validations: []admissionregistration.Validation{{
  2175  					Expression: "object.x < 100",
  2176  				}},
  2177  			},
  2178  		},
  2179  
  2180  		expectedError: `spec.matchConstraints: Required value`,
  2181  	}, {
  2182  		name: "matchConstraints.resourceRules is required",
  2183  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2184  			ObjectMeta: metav1.ObjectMeta{
  2185  				Name: "config",
  2186  			},
  2187  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2188  				Validations: []admissionregistration.Validation{{
  2189  					Expression: "object.x < 100",
  2190  				}},
  2191  				MatchConstraints: &admissionregistration.MatchResources{},
  2192  			},
  2193  		},
  2194  		expectedError: `spec.matchConstraints.resourceRules: Required value`,
  2195  	}, {
  2196  		name: "matchConstraints.resourceRules has at least one explicit rule",
  2197  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2198  			ObjectMeta: metav1.ObjectMeta{
  2199  				Name: "config",
  2200  			},
  2201  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2202  				Validations: []admissionregistration.Validation{{
  2203  					Expression: "object.x < 100",
  2204  				}},
  2205  				MatchConstraints: &admissionregistration.MatchResources{
  2206  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2207  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2208  							Rule: admissionregistration.Rule{},
  2209  						},
  2210  						ResourceNames: []string{"/./."},
  2211  					}},
  2212  				},
  2213  			},
  2214  		},
  2215  		expectedError: `spec.matchConstraints.resourceRules[0].apiVersions: Required value`,
  2216  	}, {
  2217  		name: "expression is required",
  2218  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2219  			ObjectMeta: metav1.ObjectMeta{
  2220  				Name: "config",
  2221  			},
  2222  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2223  				Validations: []admissionregistration.Validation{{}},
  2224  			},
  2225  		},
  2226  
  2227  		expectedError: `spec.validations[0].expression: Required value: expression is not specified`,
  2228  	}, {
  2229  		name: "matchResources resourceNames check",
  2230  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2231  			ObjectMeta: metav1.ObjectMeta{
  2232  				Name: "config",
  2233  			},
  2234  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2235  				Validations: []admissionregistration.Validation{{
  2236  					Expression: "object.x < 100",
  2237  				}},
  2238  				MatchConstraints: &admissionregistration.MatchResources{
  2239  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2240  						ResourceNames: []string{"/./."},
  2241  					}},
  2242  				},
  2243  			},
  2244  		},
  2245  		expectedError: `spec.matchConstraints.resourceRules[0].resourceNames[0]: Invalid value: "/./."`,
  2246  	}, {
  2247  		name: "matchResources resourceNames cannot duplicate",
  2248  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2249  			ObjectMeta: metav1.ObjectMeta{
  2250  				Name: "config",
  2251  			},
  2252  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2253  				Validations: []admissionregistration.Validation{{
  2254  					Expression: "object.x < 100",
  2255  				}},
  2256  				MatchConstraints: &admissionregistration.MatchResources{
  2257  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2258  						ResourceNames: []string{"test", "test"},
  2259  					}},
  2260  				},
  2261  			},
  2262  		},
  2263  		expectedError: `spec.matchConstraints.resourceRules[0].resourceNames[1]: Duplicate value: "test"`,
  2264  	}, {
  2265  		name: "matchResources validation: matchPolicy",
  2266  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2267  			ObjectMeta: metav1.ObjectMeta{
  2268  				Name: "config",
  2269  			},
  2270  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2271  				Validations: []admissionregistration.Validation{{
  2272  					Expression: "object.x < 100",
  2273  				}},
  2274  				MatchConstraints: &admissionregistration.MatchResources{
  2275  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  2276  						r := admissionregistration.MatchPolicyType("other")
  2277  						return &r
  2278  					}(),
  2279  				},
  2280  			},
  2281  		},
  2282  		expectedError: `spec.matchConstraints.matchPolicy: Unsupported value: "other": supported values: "Equivalent", "Exact"`,
  2283  	}, {
  2284  		name: "Operations must not be empty or nil",
  2285  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2286  			ObjectMeta: metav1.ObjectMeta{
  2287  				Name: "config",
  2288  			},
  2289  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2290  				Validations: []admissionregistration.Validation{{
  2291  					Expression: "object.x < 100",
  2292  				}},
  2293  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  2294  					r := admissionregistration.FailurePolicyType("Fail")
  2295  					return &r
  2296  				}(),
  2297  				MatchConstraints: &admissionregistration.MatchResources{
  2298  					NamespaceSelector: &metav1.LabelSelector{
  2299  						MatchLabels: map[string]string{"a": "b"},
  2300  					},
  2301  					ObjectSelector: &metav1.LabelSelector{
  2302  						MatchLabels: map[string]string{"a": "b"},
  2303  					},
  2304  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  2305  						r := admissionregistration.MatchPolicyType("Exact")
  2306  						return &r
  2307  					}(),
  2308  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2309  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2310  							Operations: []admissionregistration.OperationType{},
  2311  							Rule: admissionregistration.Rule{
  2312  								APIGroups:   []string{"a"},
  2313  								APIVersions: []string{"a"},
  2314  								Resources:   []string{"a"},
  2315  							},
  2316  						},
  2317  					}, {
  2318  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2319  							Operations: nil,
  2320  							Rule: admissionregistration.Rule{
  2321  								APIGroups:   []string{"a"},
  2322  								APIVersions: []string{"a"},
  2323  								Resources:   []string{"a"},
  2324  							},
  2325  						},
  2326  					}},
  2327  					ExcludeResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2328  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2329  							Operations: []admissionregistration.OperationType{},
  2330  							Rule: admissionregistration.Rule{
  2331  								APIGroups:   []string{"a"},
  2332  								APIVersions: []string{"a"},
  2333  								Resources:   []string{"a"},
  2334  							},
  2335  						},
  2336  					}, {
  2337  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2338  							Operations: nil,
  2339  							Rule: admissionregistration.Rule{
  2340  								APIGroups:   []string{"a"},
  2341  								APIVersions: []string{"a"},
  2342  								Resources:   []string{"a"},
  2343  							},
  2344  						},
  2345  					}},
  2346  				},
  2347  			},
  2348  		},
  2349  		expectedError: `spec.matchConstraints.resourceRules[0].operations: Required value, spec.matchConstraints.resourceRules[1].operations: Required value, spec.matchConstraints.excludeResourceRules[0].operations: Required value, spec.matchConstraints.excludeResourceRules[1].operations: Required value`,
  2350  	}, {
  2351  		name: "\"\" is NOT a valid operation",
  2352  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2353  			ObjectMeta: metav1.ObjectMeta{
  2354  				Name: "config",
  2355  			},
  2356  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2357  				Validations: []admissionregistration.Validation{{
  2358  					Expression: "object.x < 100",
  2359  				}},
  2360  				MatchConstraints: &admissionregistration.MatchResources{
  2361  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2362  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2363  							Operations: []admissionregistration.OperationType{"CREATE", ""},
  2364  							Rule: admissionregistration.Rule{
  2365  								APIGroups:   []string{"a"},
  2366  								APIVersions: []string{"a"},
  2367  								Resources:   []string{"a"},
  2368  							},
  2369  						},
  2370  					}},
  2371  				},
  2372  			},
  2373  		},
  2374  		expectedError: `Unsupported value: ""`,
  2375  	}, {
  2376  		name: "operation must be either create/update/delete/connect",
  2377  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2378  			ObjectMeta: metav1.ObjectMeta{
  2379  				Name: "config",
  2380  			},
  2381  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2382  				Validations: []admissionregistration.Validation{{
  2383  					Expression: "object.x < 100",
  2384  				}},
  2385  				MatchConstraints: &admissionregistration.MatchResources{
  2386  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2387  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2388  							Operations: []admissionregistration.OperationType{"PATCH"},
  2389  							Rule: admissionregistration.Rule{
  2390  								APIGroups:   []string{"a"},
  2391  								APIVersions: []string{"a"},
  2392  								Resources:   []string{"a"},
  2393  							},
  2394  						},
  2395  					}},
  2396  				},
  2397  			},
  2398  		},
  2399  		expectedError: `Unsupported value: "PATCH"`,
  2400  	}, {
  2401  		name: "wildcard operation cannot be mixed with other strings",
  2402  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2403  			ObjectMeta: metav1.ObjectMeta{
  2404  				Name: "config",
  2405  			},
  2406  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2407  				Validations: []admissionregistration.Validation{{
  2408  					Expression: "object.x < 100",
  2409  				}},
  2410  				MatchConstraints: &admissionregistration.MatchResources{
  2411  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2412  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2413  							Operations: []admissionregistration.OperationType{"CREATE", "*"},
  2414  							Rule: admissionregistration.Rule{
  2415  								APIGroups:   []string{"a"},
  2416  								APIVersions: []string{"a"},
  2417  								Resources:   []string{"a"},
  2418  							},
  2419  						},
  2420  					}},
  2421  				},
  2422  			},
  2423  		},
  2424  		expectedError: `if '*' is present, must not specify other operations`,
  2425  	}, {
  2426  		name: `resource "*" can co-exist with resources that have subresources`,
  2427  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2428  			ObjectMeta: metav1.ObjectMeta{
  2429  				Name: "config",
  2430  			},
  2431  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2432  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  2433  					r := admissionregistration.FailurePolicyType("Fail")
  2434  					return &r
  2435  				}(),
  2436  				Validations: []admissionregistration.Validation{{
  2437  					Expression: "object.x < 100",
  2438  				}},
  2439  				MatchConstraints: &admissionregistration.MatchResources{
  2440  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  2441  						r := admissionregistration.MatchPolicyType("Exact")
  2442  						return &r
  2443  					}(),
  2444  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2445  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2446  							Operations: []admissionregistration.OperationType{"CREATE"},
  2447  							Rule: admissionregistration.Rule{
  2448  								APIGroups:   []string{"a"},
  2449  								APIVersions: []string{"a"},
  2450  								Resources:   []string{"*", "a/b", "a/*", "*/b"},
  2451  							},
  2452  						},
  2453  					}},
  2454  					NamespaceSelector: &metav1.LabelSelector{
  2455  						MatchLabels: map[string]string{"a": "b"},
  2456  					},
  2457  					ObjectSelector: &metav1.LabelSelector{
  2458  						MatchLabels: map[string]string{"a": "b"},
  2459  					},
  2460  				},
  2461  			},
  2462  		},
  2463  	}, {
  2464  		name: `resource "*" cannot mix with resources that don't have subresources`,
  2465  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2466  			ObjectMeta: metav1.ObjectMeta{
  2467  				Name: "config",
  2468  			},
  2469  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2470  				Validations: []admissionregistration.Validation{{
  2471  					Expression: "object.x < 100",
  2472  				}},
  2473  				MatchConstraints: &admissionregistration.MatchResources{
  2474  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2475  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2476  							Operations: []admissionregistration.OperationType{"CREATE"},
  2477  							Rule: admissionregistration.Rule{
  2478  								APIGroups:   []string{"a"},
  2479  								APIVersions: []string{"a"},
  2480  								Resources:   []string{"*", "a"},
  2481  							},
  2482  						},
  2483  					}},
  2484  				},
  2485  			},
  2486  		},
  2487  		expectedError: `if '*' is present, must not specify other resources without subresources`,
  2488  	}, {
  2489  		name: "resource a/* cannot mix with a/x",
  2490  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2491  			ObjectMeta: metav1.ObjectMeta{
  2492  				Name: "config",
  2493  			},
  2494  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2495  				Validations: []admissionregistration.Validation{{
  2496  					Expression: "object.x < 100",
  2497  				}},
  2498  				MatchConstraints: &admissionregistration.MatchResources{
  2499  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2500  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2501  							Operations: []admissionregistration.OperationType{"CREATE"},
  2502  							Rule: admissionregistration.Rule{
  2503  								APIGroups:   []string{"a"},
  2504  								APIVersions: []string{"a"},
  2505  								Resources:   []string{"a/*", "a/x"},
  2506  							},
  2507  						},
  2508  					}},
  2509  				},
  2510  			},
  2511  		},
  2512  		expectedError: `spec.matchConstraints.resourceRules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`,
  2513  	}, {
  2514  		name: "resource a/* can mix with a",
  2515  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2516  			ObjectMeta: metav1.ObjectMeta{
  2517  				Name: "config",
  2518  			},
  2519  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2520  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  2521  					r := admissionregistration.FailurePolicyType("Fail")
  2522  					return &r
  2523  				}(),
  2524  				Validations: []admissionregistration.Validation{{
  2525  					Expression: "object.x < 100",
  2526  				}},
  2527  				MatchConstraints: &admissionregistration.MatchResources{
  2528  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  2529  						r := admissionregistration.MatchPolicyType("Exact")
  2530  						return &r
  2531  					}(),
  2532  					NamespaceSelector: &metav1.LabelSelector{
  2533  						MatchLabels: map[string]string{"a": "b"},
  2534  					},
  2535  					ObjectSelector: &metav1.LabelSelector{
  2536  						MatchLabels: map[string]string{"a": "b"},
  2537  					},
  2538  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2539  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2540  							Operations: []admissionregistration.OperationType{"CREATE"},
  2541  							Rule: admissionregistration.Rule{
  2542  								APIGroups:   []string{"a"},
  2543  								APIVersions: []string{"a"},
  2544  								Resources:   []string{"a/*", "a"},
  2545  							},
  2546  						},
  2547  					}},
  2548  				},
  2549  			},
  2550  		},
  2551  	}, {
  2552  		name: "resource */a cannot mix with x/a",
  2553  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2554  			ObjectMeta: metav1.ObjectMeta{
  2555  				Name: "config",
  2556  			},
  2557  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2558  				Validations: []admissionregistration.Validation{{
  2559  					Expression: "object.x < 100",
  2560  				}},
  2561  				MatchConstraints: &admissionregistration.MatchResources{
  2562  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2563  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2564  							Operations: []admissionregistration.OperationType{"CREATE"},
  2565  							Rule: admissionregistration.Rule{
  2566  								APIGroups:   []string{"a"},
  2567  								APIVersions: []string{"a"},
  2568  								Resources:   []string{"*/a", "x/a"},
  2569  							},
  2570  						},
  2571  					}},
  2572  				},
  2573  			},
  2574  		},
  2575  		expectedError: `spec.matchConstraints.resourceRules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`,
  2576  	}, {
  2577  		name: "resource */* cannot mix with other resources",
  2578  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2579  			ObjectMeta: metav1.ObjectMeta{
  2580  				Name: "config",
  2581  			},
  2582  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2583  				Validations: []admissionregistration.Validation{{
  2584  					Expression: "object.x < 100",
  2585  				}},
  2586  				MatchConstraints: &admissionregistration.MatchResources{
  2587  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2588  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2589  							Operations: []admissionregistration.OperationType{"CREATE"},
  2590  							Rule: admissionregistration.Rule{
  2591  								APIGroups:   []string{"a"},
  2592  								APIVersions: []string{"a"},
  2593  								Resources:   []string{"*/*", "a"},
  2594  							},
  2595  						},
  2596  					}},
  2597  				},
  2598  			},
  2599  		},
  2600  		expectedError: `spec.matchConstraints.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
  2601  	}, {
  2602  		name: "invalid expression",
  2603  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2604  			ObjectMeta: metav1.ObjectMeta{
  2605  				Name: "config",
  2606  			},
  2607  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2608  				Validations: []admissionregistration.Validation{{
  2609  					Expression: "object.x in [1, 2, ",
  2610  				}},
  2611  				MatchConstraints: &admissionregistration.MatchResources{
  2612  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2613  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2614  							Operations: []admissionregistration.OperationType{"CREATE"},
  2615  							Rule: admissionregistration.Rule{
  2616  								APIGroups:   []string{"a"},
  2617  								APIVersions: []string{"a"},
  2618  								Resources:   []string{"*/*"},
  2619  							},
  2620  						},
  2621  					}},
  2622  				},
  2623  			},
  2624  		},
  2625  		expectedError: `spec.validations[0].expression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: <input>:1:20: Syntax error: missing ']' at '<EOF>`,
  2626  	}, {
  2627  		name: "invalid messageExpression",
  2628  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2629  			ObjectMeta: metav1.ObjectMeta{
  2630  				Name: "config",
  2631  			},
  2632  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2633  				Validations: []admissionregistration.Validation{{
  2634  					Expression:        "true",
  2635  					MessageExpression: "object.x in [1, 2, ",
  2636  				}},
  2637  				MatchConstraints: &admissionregistration.MatchResources{
  2638  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2639  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2640  							Operations: []admissionregistration.OperationType{"CREATE"},
  2641  							Rule: admissionregistration.Rule{
  2642  								APIGroups:   []string{"a"},
  2643  								APIVersions: []string{"a"},
  2644  								Resources:   []string{"*/*"},
  2645  							},
  2646  						},
  2647  					}},
  2648  				},
  2649  			},
  2650  		},
  2651  		expectedError: `spec.validations[0].messageExpression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: <input>:1:20: Syntax error: missing ']' at '<EOF>`,
  2652  	}, {
  2653  		name: "messageExpression of wrong type",
  2654  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2655  			ObjectMeta: metav1.ObjectMeta{
  2656  				Name: "config",
  2657  			},
  2658  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2659  				Validations: []admissionregistration.Validation{{
  2660  					Expression:        "true",
  2661  					MessageExpression: "0 == 0",
  2662  				}},
  2663  				MatchConstraints: &admissionregistration.MatchResources{
  2664  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2665  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2666  							Operations: []admissionregistration.OperationType{"CREATE"},
  2667  							Rule: admissionregistration.Rule{
  2668  								APIGroups:   []string{"a"},
  2669  								APIVersions: []string{"a"},
  2670  								Resources:   []string{"*/*"},
  2671  							},
  2672  						},
  2673  					}},
  2674  				},
  2675  			},
  2676  		},
  2677  		expectedError: `spec.validations[0].messageExpression: Invalid value: "0 == 0": must evaluate to string`,
  2678  	}, {
  2679  		name: "invalid auditAnnotations key due to key name",
  2680  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2681  			ObjectMeta: metav1.ObjectMeta{
  2682  				Name: "config",
  2683  			},
  2684  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2685  				AuditAnnotations: []admissionregistration.AuditAnnotation{{
  2686  					Key:             "@",
  2687  					ValueExpression: "value",
  2688  				}},
  2689  			},
  2690  		},
  2691  		expectedError: `spec.auditAnnotations[0].key: Invalid value: "config/@": name part must consist of alphanumeric characters`,
  2692  	}, {
  2693  		name: "auditAnnotations keys must be unique",
  2694  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2695  			ObjectMeta: metav1.ObjectMeta{
  2696  				Name: "config",
  2697  			},
  2698  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2699  				AuditAnnotations: []admissionregistration.AuditAnnotation{{
  2700  					Key:             "a",
  2701  					ValueExpression: "'1'",
  2702  				}, {
  2703  					Key:             "a",
  2704  					ValueExpression: "'2'",
  2705  				}},
  2706  			},
  2707  		},
  2708  		expectedError: `spec.auditAnnotations[1].key: Duplicate value: "a"`,
  2709  	}, {
  2710  		name: "invalid auditAnnotations key due to metadata.name",
  2711  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2712  			ObjectMeta: metav1.ObjectMeta{
  2713  				Name: "nope!",
  2714  			},
  2715  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2716  				AuditAnnotations: []admissionregistration.AuditAnnotation{{
  2717  					Key:             "key",
  2718  					ValueExpression: "'value'",
  2719  				}},
  2720  			},
  2721  		},
  2722  		expectedError: `spec.auditAnnotations[0].key: Invalid value: "nope!/key": prefix part a lowercase RFC 1123 subdomain`,
  2723  	}, {
  2724  		name: "invalid auditAnnotations key due to length",
  2725  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2726  			ObjectMeta: metav1.ObjectMeta{
  2727  				Name: "this-is-a-long-name-for-a-admission-policy-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx",
  2728  			},
  2729  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2730  				AuditAnnotations: []admissionregistration.AuditAnnotation{{
  2731  					Key:             "this-is-a-long-name-for-an-audit-annotation-key-xxxxxxxxxxxxxxxxxxxxxxxxxx",
  2732  					ValueExpression: "'value'",
  2733  				}},
  2734  			},
  2735  		},
  2736  		expectedError: `spec.auditAnnotations[0].key: Invalid value`,
  2737  	}, {
  2738  		name: "invalid auditAnnotations valueExpression type",
  2739  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2740  			ObjectMeta: metav1.ObjectMeta{
  2741  				Name: "config",
  2742  			},
  2743  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2744  				AuditAnnotations: []admissionregistration.AuditAnnotation{{
  2745  					Key:             "something",
  2746  					ValueExpression: "true",
  2747  				}},
  2748  			},
  2749  		},
  2750  		expectedError: `spec.auditAnnotations[0].valueExpression: Invalid value: "true": must evaluate to one of [string null_type]`,
  2751  	}, {
  2752  		name: "invalid auditAnnotations valueExpression",
  2753  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2754  			ObjectMeta: metav1.ObjectMeta{
  2755  				Name: "config",
  2756  			},
  2757  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2758  				AuditAnnotations: []admissionregistration.AuditAnnotation{{
  2759  					Key:             "something",
  2760  					ValueExpression: "object.x in [1, 2, ",
  2761  				}},
  2762  			},
  2763  		},
  2764  		expectedError: `spec.auditAnnotations[0].valueExpression: Invalid value: "object.x in [1, 2, ": compilation failed: ERROR: <input>:1:19: Syntax error: missing ']' at '<EOF>`,
  2765  	}, {
  2766  		name: "single match condition must have a name",
  2767  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2768  			ObjectMeta: metav1.ObjectMeta{
  2769  				Name: "config",
  2770  			},
  2771  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2772  				MatchConditions: []admissionregistration.MatchCondition{{
  2773  					Expression: "true",
  2774  				}},
  2775  				Validations: []admissionregistration.Validation{{
  2776  					Expression: "object.x < 100",
  2777  				}},
  2778  			},
  2779  		},
  2780  		expectedError: `spec.matchConditions[0].name: Required value`,
  2781  	}, {
  2782  		name: "match condition with parameters allowed",
  2783  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2784  			ObjectMeta: metav1.ObjectMeta{
  2785  				Name: "config",
  2786  			},
  2787  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2788  				ParamKind: &admissionregistration.ParamKind{
  2789  					Kind:       "Foo",
  2790  					APIVersion: "foobar/v1alpha1",
  2791  				},
  2792  				MatchConstraints: &admissionregistration.MatchResources{
  2793  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2794  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2795  							Operations: []admissionregistration.OperationType{"*"},
  2796  							Rule: admissionregistration.Rule{
  2797  								APIGroups:   []string{"a"},
  2798  								APIVersions: []string{"a"},
  2799  								Resources:   []string{"a"},
  2800  							},
  2801  						},
  2802  					}},
  2803  					NamespaceSelector: &metav1.LabelSelector{
  2804  						MatchLabels: map[string]string{"a": "b"},
  2805  					},
  2806  					ObjectSelector: &metav1.LabelSelector{
  2807  						MatchLabels: map[string]string{"a": "b"},
  2808  					},
  2809  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  2810  						r := admissionregistration.MatchPolicyType("Exact")
  2811  						return &r
  2812  					}(),
  2813  				},
  2814  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  2815  					r := admissionregistration.FailurePolicyType("Fail")
  2816  					return &r
  2817  				}(),
  2818  				MatchConditions: []admissionregistration.MatchCondition{{
  2819  					Name:       "hasParams",
  2820  					Expression: `params.foo == "okay"`,
  2821  				}},
  2822  				Validations: []admissionregistration.Validation{{
  2823  					Expression: "object.x < 100",
  2824  				}},
  2825  			},
  2826  		},
  2827  		expectedError: "",
  2828  	}, {
  2829  		name: "match condition with parameters not allowed if no param kind",
  2830  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2831  			ObjectMeta: metav1.ObjectMeta{
  2832  				Name: "config",
  2833  			},
  2834  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2835  				MatchConstraints: &admissionregistration.MatchResources{
  2836  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  2837  						RuleWithOperations: admissionregistration.RuleWithOperations{
  2838  							Operations: []admissionregistration.OperationType{"*"},
  2839  							Rule: admissionregistration.Rule{
  2840  								APIGroups:   []string{"a"},
  2841  								APIVersions: []string{"a"},
  2842  								Resources:   []string{"a"},
  2843  							},
  2844  						},
  2845  					}},
  2846  					NamespaceSelector: &metav1.LabelSelector{
  2847  						MatchLabels: map[string]string{"a": "b"},
  2848  					},
  2849  					ObjectSelector: &metav1.LabelSelector{
  2850  						MatchLabels: map[string]string{"a": "b"},
  2851  					},
  2852  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  2853  						r := admissionregistration.MatchPolicyType("Exact")
  2854  						return &r
  2855  					}(),
  2856  				},
  2857  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  2858  					r := admissionregistration.FailurePolicyType("Fail")
  2859  					return &r
  2860  				}(),
  2861  				MatchConditions: []admissionregistration.MatchCondition{{
  2862  					Name:       "hasParams",
  2863  					Expression: `params.foo == "okay"`,
  2864  				}},
  2865  				Validations: []admissionregistration.Validation{{
  2866  					Expression: "object.x < 100",
  2867  				}},
  2868  			},
  2869  		},
  2870  		expectedError: `undeclared reference to 'params'`,
  2871  	}, {
  2872  		name: "variable composition empty name",
  2873  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2874  			ObjectMeta: metav1.ObjectMeta{
  2875  				Name: "config",
  2876  			},
  2877  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2878  				Variables: []admissionregistration.Variable{
  2879  					{
  2880  						Name:       "    ",
  2881  						Expression: "true",
  2882  					},
  2883  				},
  2884  				Validations: []admissionregistration.Validation{
  2885  					{
  2886  						Expression: "true",
  2887  					},
  2888  				},
  2889  			},
  2890  		},
  2891  		expectedError: `spec.variables[0].name: Required value: name is not specified`,
  2892  	}, {
  2893  		name: "variable composition name is not a valid identifier",
  2894  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2895  			ObjectMeta: metav1.ObjectMeta{
  2896  				Name: "config",
  2897  			},
  2898  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2899  				Variables: []admissionregistration.Variable{
  2900  					{
  2901  						Name:       "4ever",
  2902  						Expression: "true",
  2903  					},
  2904  				},
  2905  				Validations: []admissionregistration.Validation{
  2906  					{
  2907  						Expression: "true",
  2908  					},
  2909  				},
  2910  			},
  2911  		},
  2912  		expectedError: `spec.variables[0].name: Invalid value: "4ever": name is not a valid CEL identifier`,
  2913  	}, {
  2914  		name: "variable composition cannot compile",
  2915  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2916  			ObjectMeta: metav1.ObjectMeta{
  2917  				Name: "config",
  2918  			},
  2919  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2920  				Variables: []admissionregistration.Variable{
  2921  					{
  2922  						Name:       "foo",
  2923  						Expression: "114 + '514'", // compile error: type confusion
  2924  					},
  2925  				},
  2926  				Validations: []admissionregistration.Validation{
  2927  					{
  2928  						Expression: "true",
  2929  					},
  2930  				},
  2931  			},
  2932  		},
  2933  		expectedError: `spec.variables[0].expression: Invalid value: "114 + '514'": compilation failed: ERROR: <input>:1:5: found no matching overload for '_+_' applied to '(int, string)`,
  2934  	}, {
  2935  		name: "validation referred to non-existing variable",
  2936  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2937  			ObjectMeta: metav1.ObjectMeta{
  2938  				Name: "config",
  2939  			},
  2940  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2941  				Variables: []admissionregistration.Variable{
  2942  					{
  2943  						Name:       "foo",
  2944  						Expression: "1 + 1",
  2945  					},
  2946  					{
  2947  						Name:       "bar",
  2948  						Expression: "variables.foo + 1",
  2949  					},
  2950  				},
  2951  				Validations: []admissionregistration.Validation{
  2952  					{
  2953  						Expression: "variables.foo > 1", // correct
  2954  					},
  2955  					{
  2956  						Expression: "variables.replicas == 2", // replicas undefined
  2957  					},
  2958  				},
  2959  			},
  2960  		},
  2961  		expectedError: `spec.validations[1].expression: Invalid value: "variables.replicas == 2": compilation failed: ERROR: <input>:1:10: undefined field 'replicas'`,
  2962  	}, {
  2963  		name: "variables wrong order",
  2964  		config: &admissionregistration.ValidatingAdmissionPolicy{
  2965  			ObjectMeta: metav1.ObjectMeta{
  2966  				Name: "config",
  2967  			},
  2968  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  2969  				Variables: []admissionregistration.Variable{
  2970  					{
  2971  						Name:       "correct",
  2972  						Expression: "object",
  2973  					},
  2974  					{
  2975  						Name:       "bar", // should go below foo
  2976  						Expression: "variables.foo + 1",
  2977  					},
  2978  					{
  2979  						Name:       "foo",
  2980  						Expression: "1 + 1",
  2981  					},
  2982  				},
  2983  				Validations: []admissionregistration.Validation{
  2984  					{
  2985  						Expression: "variables.foo > 1", // correct
  2986  					},
  2987  				},
  2988  			},
  2989  		},
  2990  		expectedError: `spec.variables[1].expression: Invalid value: "variables.foo + 1": compilation failed: ERROR: <input>:1:10: undefined field 'foo'`,
  2991  	},
  2992  	}
  2993  	for _, test := range tests {
  2994  		t.Run(test.name, func(t *testing.T) {
  2995  			errs := ValidateValidatingAdmissionPolicy(test.config)
  2996  			err := errs.ToAggregate()
  2997  			if err != nil {
  2998  				if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  2999  					t.Errorf("expected to contain %s, got %s", e, a)
  3000  				}
  3001  			} else {
  3002  				if test.expectedError != "" {
  3003  					t.Errorf("unexpected no error, expected to contain %s", test.expectedError)
  3004  				}
  3005  			}
  3006  		})
  3007  	}
  3008  }
  3009  
  3010  func TestValidateValidatingAdmissionPolicyUpdate(t *testing.T) {
  3011  	tests := []struct {
  3012  		name          string
  3013  		oldconfig     *admissionregistration.ValidatingAdmissionPolicy
  3014  		config        *admissionregistration.ValidatingAdmissionPolicy
  3015  		expectedError string
  3016  	}{{
  3017  		name: "should pass on valid new ValidatingAdmissionPolicy",
  3018  		config: &admissionregistration.ValidatingAdmissionPolicy{
  3019  			ObjectMeta: metav1.ObjectMeta{
  3020  				Name: "config",
  3021  			},
  3022  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  3023  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  3024  					r := admissionregistration.FailurePolicyType("Fail")
  3025  					return &r
  3026  				}(),
  3027  				Validations: []admissionregistration.Validation{{
  3028  					Expression: "object.x < 100",
  3029  				}},
  3030  				MatchConstraints: &admissionregistration.MatchResources{
  3031  					NamespaceSelector: &metav1.LabelSelector{
  3032  						MatchLabels: map[string]string{"a": "b"},
  3033  					},
  3034  					ObjectSelector: &metav1.LabelSelector{
  3035  						MatchLabels: map[string]string{"a": "b"},
  3036  					},
  3037  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3038  						r := admissionregistration.MatchPolicyType("Exact")
  3039  						return &r
  3040  					}(),
  3041  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3042  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3043  							Operations: []admissionregistration.OperationType{"CREATE"},
  3044  							Rule: admissionregistration.Rule{
  3045  								APIGroups:   []string{"a"},
  3046  								APIVersions: []string{"a"},
  3047  								Resources:   []string{"a"},
  3048  							},
  3049  						},
  3050  					}},
  3051  				},
  3052  			},
  3053  		},
  3054  		oldconfig: &admissionregistration.ValidatingAdmissionPolicy{
  3055  			ObjectMeta: metav1.ObjectMeta{
  3056  				Name: "config",
  3057  			},
  3058  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  3059  				Validations: []admissionregistration.Validation{{
  3060  					Expression: "object.x < 100",
  3061  				}},
  3062  				MatchConstraints: &admissionregistration.MatchResources{
  3063  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3064  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3065  							Operations: []admissionregistration.OperationType{"CREATE"},
  3066  							Rule: admissionregistration.Rule{
  3067  								APIGroups:   []string{"a"},
  3068  								APIVersions: []string{"a"},
  3069  								Resources:   []string{"a"},
  3070  							},
  3071  						},
  3072  					}},
  3073  				},
  3074  			},
  3075  		},
  3076  	}, {
  3077  		name: "should pass on valid new ValidatingAdmissionPolicy with invalid old ValidatingAdmissionPolicy",
  3078  		config: &admissionregistration.ValidatingAdmissionPolicy{
  3079  			ObjectMeta: metav1.ObjectMeta{
  3080  				Name: "config",
  3081  			},
  3082  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  3083  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  3084  					r := admissionregistration.FailurePolicyType("Fail")
  3085  					return &r
  3086  				}(),
  3087  				Validations: []admissionregistration.Validation{{
  3088  					Expression: "object.x < 100",
  3089  				}},
  3090  				MatchConstraints: &admissionregistration.MatchResources{
  3091  					NamespaceSelector: &metav1.LabelSelector{
  3092  						MatchLabels: map[string]string{"a": "b"},
  3093  					},
  3094  					ObjectSelector: &metav1.LabelSelector{
  3095  						MatchLabels: map[string]string{"a": "b"},
  3096  					},
  3097  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3098  						r := admissionregistration.MatchPolicyType("Exact")
  3099  						return &r
  3100  					}(),
  3101  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3102  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3103  							Operations: []admissionregistration.OperationType{"CREATE"},
  3104  							Rule: admissionregistration.Rule{
  3105  								APIGroups:   []string{"a"},
  3106  								APIVersions: []string{"a"},
  3107  								Resources:   []string{"a"},
  3108  							},
  3109  						},
  3110  					}},
  3111  				},
  3112  			},
  3113  		},
  3114  		oldconfig: &admissionregistration.ValidatingAdmissionPolicy{
  3115  			ObjectMeta: metav1.ObjectMeta{
  3116  				Name: "!!!",
  3117  			},
  3118  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{},
  3119  		},
  3120  	}, {
  3121  		name: "match conditions re-checked if paramKind changes",
  3122  		oldconfig: &admissionregistration.ValidatingAdmissionPolicy{
  3123  			ObjectMeta: metav1.ObjectMeta{
  3124  				Name: "config",
  3125  			},
  3126  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  3127  				ParamKind: &admissionregistration.ParamKind{
  3128  					Kind:       "Foo",
  3129  					APIVersion: "foobar/v1alpha1",
  3130  				},
  3131  				MatchConstraints: &admissionregistration.MatchResources{
  3132  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3133  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3134  							Operations: []admissionregistration.OperationType{"*"},
  3135  							Rule: admissionregistration.Rule{
  3136  								APIGroups:   []string{"a"},
  3137  								APIVersions: []string{"a"},
  3138  								Resources:   []string{"a"},
  3139  							},
  3140  						},
  3141  					}},
  3142  					NamespaceSelector: &metav1.LabelSelector{
  3143  						MatchLabels: map[string]string{"a": "b"},
  3144  					},
  3145  					ObjectSelector: &metav1.LabelSelector{
  3146  						MatchLabels: map[string]string{"a": "b"},
  3147  					},
  3148  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3149  						r := admissionregistration.MatchPolicyType("Exact")
  3150  						return &r
  3151  					}(),
  3152  				},
  3153  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  3154  					r := admissionregistration.FailurePolicyType("Fail")
  3155  					return &r
  3156  				}(),
  3157  				MatchConditions: []admissionregistration.MatchCondition{{
  3158  					Name:       "hasParams",
  3159  					Expression: `params.foo == "okay"`,
  3160  				}},
  3161  				Validations: []admissionregistration.Validation{{
  3162  					Expression: "object.x < 100",
  3163  				}},
  3164  			},
  3165  		},
  3166  		config: &admissionregistration.ValidatingAdmissionPolicy{
  3167  			ObjectMeta: metav1.ObjectMeta{
  3168  				Name: "config",
  3169  			},
  3170  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  3171  				MatchConstraints: &admissionregistration.MatchResources{
  3172  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3173  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3174  							Operations: []admissionregistration.OperationType{"*"},
  3175  							Rule: admissionregistration.Rule{
  3176  								APIGroups:   []string{"a"},
  3177  								APIVersions: []string{"a"},
  3178  								Resources:   []string{"a"},
  3179  							},
  3180  						},
  3181  					}},
  3182  					NamespaceSelector: &metav1.LabelSelector{
  3183  						MatchLabels: map[string]string{"a": "b"},
  3184  					},
  3185  					ObjectSelector: &metav1.LabelSelector{
  3186  						MatchLabels: map[string]string{"a": "b"},
  3187  					},
  3188  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3189  						r := admissionregistration.MatchPolicyType("Exact")
  3190  						return &r
  3191  					}(),
  3192  				},
  3193  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  3194  					r := admissionregistration.FailurePolicyType("Fail")
  3195  					return &r
  3196  				}(),
  3197  				MatchConditions: []admissionregistration.MatchCondition{{
  3198  					Name:       "hasParams",
  3199  					Expression: `params.foo == "okay"`,
  3200  				}},
  3201  				Validations: []admissionregistration.Validation{{
  3202  					Expression: "object.x < 100",
  3203  				}},
  3204  			},
  3205  		},
  3206  		expectedError: `undeclared reference to 'params'`,
  3207  	}, {
  3208  		name: "match conditions not re-checked if no change to paramKind or matchConditions",
  3209  		oldconfig: &admissionregistration.ValidatingAdmissionPolicy{
  3210  			ObjectMeta: metav1.ObjectMeta{
  3211  				Name: "config",
  3212  			},
  3213  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  3214  				MatchConstraints: &admissionregistration.MatchResources{
  3215  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3216  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3217  							Operations: []admissionregistration.OperationType{"*"},
  3218  							Rule: admissionregistration.Rule{
  3219  								APIGroups:   []string{"a"},
  3220  								APIVersions: []string{"a"},
  3221  								Resources:   []string{"a"},
  3222  							},
  3223  						},
  3224  					}},
  3225  					NamespaceSelector: &metav1.LabelSelector{
  3226  						MatchLabels: map[string]string{"a": "b"},
  3227  					},
  3228  					ObjectSelector: &metav1.LabelSelector{
  3229  						MatchLabels: map[string]string{"a": "b"},
  3230  					},
  3231  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3232  						r := admissionregistration.MatchPolicyType("Exact")
  3233  						return &r
  3234  					}(),
  3235  				},
  3236  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  3237  					r := admissionregistration.FailurePolicyType("Fail")
  3238  					return &r
  3239  				}(),
  3240  				MatchConditions: []admissionregistration.MatchCondition{{
  3241  					Name:       "hasParams",
  3242  					Expression: `params.foo == "okay"`,
  3243  				}},
  3244  				Validations: []admissionregistration.Validation{{
  3245  					Expression: "object.x < 100",
  3246  				}},
  3247  			},
  3248  		},
  3249  		config: &admissionregistration.ValidatingAdmissionPolicy{
  3250  			ObjectMeta: metav1.ObjectMeta{
  3251  				Name: "config",
  3252  			},
  3253  			Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  3254  				MatchConstraints: &admissionregistration.MatchResources{
  3255  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3256  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3257  							Operations: []admissionregistration.OperationType{"*"},
  3258  							Rule: admissionregistration.Rule{
  3259  								APIGroups:   []string{"a"},
  3260  								APIVersions: []string{"a"},
  3261  								Resources:   []string{"a"},
  3262  							},
  3263  						},
  3264  					}},
  3265  					NamespaceSelector: &metav1.LabelSelector{
  3266  						MatchLabels: map[string]string{"a": "b"},
  3267  					},
  3268  					ObjectSelector: &metav1.LabelSelector{
  3269  						MatchLabels: map[string]string{"a": "b"},
  3270  					},
  3271  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3272  						r := admissionregistration.MatchPolicyType("Exact")
  3273  						return &r
  3274  					}(),
  3275  				},
  3276  				FailurePolicy: func() *admissionregistration.FailurePolicyType {
  3277  					r := admissionregistration.FailurePolicyType("Ignore")
  3278  					return &r
  3279  				}(),
  3280  				MatchConditions: []admissionregistration.MatchCondition{{
  3281  					Name:       "hasParams",
  3282  					Expression: `params.foo == "okay"`,
  3283  				}},
  3284  				Validations: []admissionregistration.Validation{{
  3285  					Expression: "object.x < 50",
  3286  				}},
  3287  			},
  3288  		},
  3289  		expectedError: "",
  3290  	},
  3291  		{
  3292  			name: "expressions that are not changed must be compiled using the StoredExpression environment",
  3293  			oldconfig: validatingAdmissionPolicyWithExpressions(
  3294  				[]admissionregistration.MatchCondition{
  3295  					{
  3296  						Name:       "checkEnvironmentMode",
  3297  						Expression: `test() == true`,
  3298  					},
  3299  				},
  3300  				[]admissionregistration.Validation{
  3301  					{
  3302  						Expression:        `test() == true`,
  3303  						MessageExpression: "string(test())",
  3304  					},
  3305  				},
  3306  				[]admissionregistration.AuditAnnotation{
  3307  					{
  3308  						Key:             "checkEnvironmentMode",
  3309  						ValueExpression: "string(test())",
  3310  					},
  3311  				}),
  3312  			config: validatingAdmissionPolicyWithExpressions(
  3313  				[]admissionregistration.MatchCondition{
  3314  					{
  3315  						Name:       "checkEnvironmentMode",
  3316  						Expression: `test() == true`,
  3317  					},
  3318  				},
  3319  				[]admissionregistration.Validation{
  3320  					{
  3321  						Expression:        `test() == true`,
  3322  						MessageExpression: "string(test())",
  3323  					},
  3324  				},
  3325  				[]admissionregistration.AuditAnnotation{
  3326  					{
  3327  						Key:             "checkEnvironmentMode",
  3328  						ValueExpression: "string(test())",
  3329  					},
  3330  				}),
  3331  			expectedError: "",
  3332  		},
  3333  		{
  3334  			name: "matchCondition expressions that are changed must be compiled using the NewExpression environment",
  3335  			oldconfig: validatingAdmissionPolicyWithExpressions(
  3336  				[]admissionregistration.MatchCondition{
  3337  					{
  3338  						Name:       "checkEnvironmentMode",
  3339  						Expression: `true`,
  3340  					},
  3341  				},
  3342  				nil, nil),
  3343  			config: validatingAdmissionPolicyWithExpressions(
  3344  				[]admissionregistration.MatchCondition{
  3345  					{
  3346  						Name:       "checkEnvironmentMode",
  3347  						Expression: `test() == true`,
  3348  					},
  3349  				},
  3350  				nil, nil),
  3351  			expectedError: `undeclared reference to 'test'`,
  3352  		},
  3353  		{
  3354  			name: "validation expressions that are changed must be compiled using the NewExpression environment",
  3355  			oldconfig: validatingAdmissionPolicyWithExpressions(
  3356  				nil,
  3357  				[]admissionregistration.Validation{
  3358  					{
  3359  						Expression: `true`,
  3360  					},
  3361  				},
  3362  				nil),
  3363  			config: validatingAdmissionPolicyWithExpressions(
  3364  				nil,
  3365  				[]admissionregistration.Validation{
  3366  					{
  3367  						Expression: `test() == true`,
  3368  					},
  3369  				},
  3370  				nil),
  3371  			expectedError: `undeclared reference to 'test'`,
  3372  		},
  3373  		{
  3374  			name: "validation messageExpressions that are changed must be compiled using the NewExpression environment",
  3375  			oldconfig: validatingAdmissionPolicyWithExpressions(
  3376  				nil,
  3377  				[]admissionregistration.Validation{
  3378  					{
  3379  						Expression:        `true`,
  3380  						MessageExpression: "'test'",
  3381  					},
  3382  				},
  3383  				nil),
  3384  			config: validatingAdmissionPolicyWithExpressions(
  3385  				nil,
  3386  				[]admissionregistration.Validation{
  3387  					{
  3388  						Expression:        `true`,
  3389  						MessageExpression: "string(test())",
  3390  					},
  3391  				},
  3392  				nil),
  3393  			expectedError: `undeclared reference to 'test'`,
  3394  		},
  3395  		{
  3396  			name: "auditAnnotation valueExpressions that are changed must be compiled using the NewExpression environment",
  3397  			oldconfig: validatingAdmissionPolicyWithExpressions(
  3398  				nil, nil,
  3399  				[]admissionregistration.AuditAnnotation{
  3400  					{
  3401  						Key:             "checkEnvironmentMode",
  3402  						ValueExpression: "'test'",
  3403  					},
  3404  				}),
  3405  			config: validatingAdmissionPolicyWithExpressions(
  3406  				nil, nil,
  3407  				[]admissionregistration.AuditAnnotation{
  3408  					{
  3409  						Key:             "checkEnvironmentMode",
  3410  						ValueExpression: "string(test())",
  3411  					},
  3412  				}),
  3413  			expectedError: `undeclared reference to 'test'`,
  3414  		},
  3415  		// TODO: CustomAuditAnnotations: string valueExpression with {oldObject} is allowed
  3416  	}
  3417  	strictCost := utilfeature.DefaultFeatureGate.Enabled(features.StrictCostEnforcementForVAP)
  3418  	// Include the test library, which includes the test() function in the storage environment during test
  3419  	base := environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost)
  3420  	extended, err := base.Extend(environment.VersionedOptions{
  3421  		IntroducedVersion: version.MustParseGeneric("1.999"),
  3422  		EnvOptions:        []cel.EnvOption{library.Test()},
  3423  	})
  3424  	if err != nil {
  3425  		t.Fatal(err)
  3426  	}
  3427  	if strictCost {
  3428  		strictStatelessCELCompiler = plugincel.NewCompiler(extended)
  3429  		defer func() {
  3430  			strictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost))
  3431  		}()
  3432  	} else {
  3433  		nonStrictStatelessCELCompiler = plugincel.NewCompiler(extended)
  3434  		defer func() {
  3435  			nonStrictStatelessCELCompiler = plugincel.NewCompiler(environment.MustBaseEnvSet(environment.DefaultCompatibilityVersion(), strictCost))
  3436  		}()
  3437  	}
  3438  
  3439  	for _, test := range tests {
  3440  		t.Run(test.name, func(t *testing.T) {
  3441  			errs := ValidateValidatingAdmissionPolicyUpdate(test.config, test.oldconfig)
  3442  			err := errs.ToAggregate()
  3443  			if err != nil {
  3444  				if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  3445  					t.Errorf("expected to contain %s, got %s", e, a)
  3446  				}
  3447  			} else {
  3448  				if test.expectedError != "" {
  3449  					t.Errorf("unexpected no error, expected to contain %s", test.expectedError)
  3450  				}
  3451  			}
  3452  		})
  3453  
  3454  	}
  3455  }
  3456  
  3457  func validatingAdmissionPolicyWithExpressions(
  3458  	matchConditions []admissionregistration.MatchCondition,
  3459  	validations []admissionregistration.Validation,
  3460  	auditAnnotations []admissionregistration.AuditAnnotation) *admissionregistration.ValidatingAdmissionPolicy {
  3461  	return &admissionregistration.ValidatingAdmissionPolicy{
  3462  		ObjectMeta: metav1.ObjectMeta{
  3463  			Name: "config",
  3464  		},
  3465  		Spec: admissionregistration.ValidatingAdmissionPolicySpec{
  3466  			MatchConstraints: &admissionregistration.MatchResources{
  3467  				ResourceRules: []admissionregistration.NamedRuleWithOperations{
  3468  					{
  3469  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3470  							Operations: []admissionregistration.OperationType{"*"},
  3471  							Rule: admissionregistration.Rule{
  3472  								APIGroups:   []string{"a"},
  3473  								APIVersions: []string{"a"},
  3474  								Resources:   []string{"a"},
  3475  							},
  3476  						},
  3477  					},
  3478  				},
  3479  				NamespaceSelector: &metav1.LabelSelector{
  3480  					MatchLabels: map[string]string{"a": "b"},
  3481  				},
  3482  				ObjectSelector: &metav1.LabelSelector{
  3483  					MatchLabels: map[string]string{"a": "b"},
  3484  				},
  3485  				MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3486  					r := admissionregistration.MatchPolicyType("Exact")
  3487  					return &r
  3488  				}(),
  3489  			},
  3490  			FailurePolicy: func() *admissionregistration.FailurePolicyType {
  3491  				r := admissionregistration.FailurePolicyType("Ignore")
  3492  				return &r
  3493  			}(),
  3494  			MatchConditions:  matchConditions,
  3495  			Validations:      validations,
  3496  			AuditAnnotations: auditAnnotations,
  3497  		},
  3498  	}
  3499  }
  3500  
  3501  func TestValidateValidatingAdmissionPolicyBinding(t *testing.T) {
  3502  	tests := []struct {
  3503  		name          string
  3504  		config        *admissionregistration.ValidatingAdmissionPolicyBinding
  3505  		expectedError string
  3506  	}{{
  3507  		name: "metadata.name validation",
  3508  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3509  			ObjectMeta: metav1.ObjectMeta{
  3510  				Name: "!!!!",
  3511  			},
  3512  		},
  3513  		expectedError: `metadata.name: Invalid value: "!!!!":`,
  3514  	}, {
  3515  		name: "PolicyName is required",
  3516  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3517  			ObjectMeta: metav1.ObjectMeta{
  3518  				Name: "config",
  3519  			},
  3520  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{},
  3521  		},
  3522  		expectedError: `spec.policyName: Required value`,
  3523  	}, {
  3524  		name: "matchResources validation: matchPolicy",
  3525  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3526  			ObjectMeta: metav1.ObjectMeta{
  3527  				Name: "config",
  3528  			},
  3529  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3530  				PolicyName: "xyzlimit-scale.example.com",
  3531  				ParamRef: &admissionregistration.ParamRef{
  3532  					Name:                    "xyzlimit-scale-setting.example.com",
  3533  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3534  				},
  3535  				MatchResources: &admissionregistration.MatchResources{
  3536  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3537  						r := admissionregistration.MatchPolicyType("other")
  3538  						return &r
  3539  					}(),
  3540  				},
  3541  			},
  3542  		},
  3543  		expectedError: `spec.matchResouces.matchPolicy: Unsupported value: "other": supported values: "Equivalent", "Exact"`,
  3544  	}, {
  3545  		name: "Operations must not be empty or nil",
  3546  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3547  			ObjectMeta: metav1.ObjectMeta{
  3548  				Name: "config",
  3549  			},
  3550  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3551  				PolicyName: "xyzlimit-scale.example.com",
  3552  				ParamRef: &admissionregistration.ParamRef{
  3553  					Name:                    "xyzlimit-scale-setting.example.com",
  3554  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3555  				},
  3556  				MatchResources: &admissionregistration.MatchResources{
  3557  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3558  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3559  							Operations: []admissionregistration.OperationType{},
  3560  							Rule: admissionregistration.Rule{
  3561  								APIGroups:   []string{"a"},
  3562  								APIVersions: []string{"a"},
  3563  								Resources:   []string{"a"},
  3564  							},
  3565  						},
  3566  					}, {
  3567  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3568  							Operations: nil,
  3569  							Rule: admissionregistration.Rule{
  3570  								APIGroups:   []string{"a"},
  3571  								APIVersions: []string{"a"},
  3572  								Resources:   []string{"a"},
  3573  							},
  3574  						},
  3575  					}},
  3576  					ExcludeResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3577  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3578  							Operations: []admissionregistration.OperationType{},
  3579  							Rule: admissionregistration.Rule{
  3580  								APIGroups:   []string{"a"},
  3581  								APIVersions: []string{"a"},
  3582  								Resources:   []string{"a"},
  3583  							},
  3584  						},
  3585  					}, {
  3586  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3587  							Operations: nil,
  3588  							Rule: admissionregistration.Rule{
  3589  								APIGroups:   []string{"a"},
  3590  								APIVersions: []string{"a"},
  3591  								Resources:   []string{"a"},
  3592  							},
  3593  						},
  3594  					}},
  3595  				},
  3596  			},
  3597  		},
  3598  		expectedError: `spec.matchResouces.resourceRules[0].operations: Required value, spec.matchResouces.resourceRules[1].operations: Required value, spec.matchResouces.excludeResourceRules[0].operations: Required value, spec.matchResouces.excludeResourceRules[1].operations: Required value`,
  3599  	}, {
  3600  		name: "\"\" is NOT a valid operation",
  3601  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3602  			ObjectMeta: metav1.ObjectMeta{
  3603  				Name: "config",
  3604  			},
  3605  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3606  				PolicyName: "xyzlimit-scale.example.com",
  3607  				ParamRef: &admissionregistration.ParamRef{
  3608  					Name:                    "xyzlimit-scale-setting.example.com",
  3609  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3610  				}, MatchResources: &admissionregistration.MatchResources{
  3611  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3612  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3613  							Operations: []admissionregistration.OperationType{"CREATE", ""},
  3614  							Rule: admissionregistration.Rule{
  3615  								APIGroups:   []string{"a"},
  3616  								APIVersions: []string{"a"},
  3617  								Resources:   []string{"a"},
  3618  							},
  3619  						},
  3620  					}},
  3621  				},
  3622  			},
  3623  		},
  3624  		expectedError: `Unsupported value: ""`,
  3625  	}, {
  3626  		name: "operation must be either create/update/delete/connect",
  3627  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3628  			ObjectMeta: metav1.ObjectMeta{
  3629  				Name: "config",
  3630  			},
  3631  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3632  				PolicyName: "xyzlimit-scale.example.com",
  3633  				ParamRef: &admissionregistration.ParamRef{
  3634  					Name:                    "xyzlimit-scale-setting.example.com",
  3635  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3636  				}, MatchResources: &admissionregistration.MatchResources{
  3637  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3638  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3639  							Operations: []admissionregistration.OperationType{"PATCH"},
  3640  							Rule: admissionregistration.Rule{
  3641  								APIGroups:   []string{"a"},
  3642  								APIVersions: []string{"a"},
  3643  								Resources:   []string{"a"},
  3644  							},
  3645  						},
  3646  					}},
  3647  				},
  3648  			},
  3649  		},
  3650  		expectedError: `Unsupported value: "PATCH"`,
  3651  	}, {
  3652  		name: "wildcard operation cannot be mixed with other strings",
  3653  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3654  			ObjectMeta: metav1.ObjectMeta{
  3655  				Name: "config",
  3656  			},
  3657  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3658  				PolicyName: "xyzlimit-scale.example.com",
  3659  				ParamRef: &admissionregistration.ParamRef{
  3660  					Name:                    "xyzlimit-scale-setting.example.com",
  3661  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3662  				},
  3663  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3664  				MatchResources: &admissionregistration.MatchResources{
  3665  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3666  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3667  							Operations: []admissionregistration.OperationType{"CREATE", "*"},
  3668  							Rule: admissionregistration.Rule{
  3669  								APIGroups:   []string{"a"},
  3670  								APIVersions: []string{"a"},
  3671  								Resources:   []string{"a"},
  3672  							},
  3673  						},
  3674  					}},
  3675  				},
  3676  			},
  3677  		},
  3678  		expectedError: `if '*' is present, must not specify other operations`,
  3679  	}, {
  3680  		name: `resource "*" can co-exist with resources that have subresources`,
  3681  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3682  			ObjectMeta: metav1.ObjectMeta{
  3683  				Name: "config",
  3684  			},
  3685  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3686  				PolicyName: "xyzlimit-scale.example.com",
  3687  				ParamRef: &admissionregistration.ParamRef{
  3688  					Name:                    "xyzlimit-scale-setting.example.com",
  3689  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3690  				},
  3691  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3692  				MatchResources: &admissionregistration.MatchResources{
  3693  					NamespaceSelector: &metav1.LabelSelector{
  3694  						MatchLabels: map[string]string{"a": "b"},
  3695  					},
  3696  					ObjectSelector: &metav1.LabelSelector{
  3697  						MatchLabels: map[string]string{"a": "b"},
  3698  					},
  3699  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3700  						r := admissionregistration.MatchPolicyType("Exact")
  3701  						return &r
  3702  					}(),
  3703  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3704  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3705  							Operations: []admissionregistration.OperationType{"CREATE"},
  3706  							Rule: admissionregistration.Rule{
  3707  								APIGroups:   []string{"a"},
  3708  								APIVersions: []string{"a"},
  3709  								Resources:   []string{"*", "a/b", "a/*", "*/b"},
  3710  							},
  3711  						},
  3712  					}},
  3713  				},
  3714  			},
  3715  		},
  3716  	}, {
  3717  		name: `resource "*" cannot mix with resources that don't have subresources`,
  3718  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3719  			ObjectMeta: metav1.ObjectMeta{
  3720  				Name: "config",
  3721  			},
  3722  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3723  				PolicyName: "xyzlimit-scale.example.com",
  3724  				ParamRef: &admissionregistration.ParamRef{
  3725  					Name:                    "xyzlimit-scale-setting.example.com",
  3726  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3727  				},
  3728  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3729  				MatchResources: &admissionregistration.MatchResources{
  3730  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3731  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3732  							Operations: []admissionregistration.OperationType{"CREATE"},
  3733  							Rule: admissionregistration.Rule{
  3734  								APIGroups:   []string{"a"},
  3735  								APIVersions: []string{"a"},
  3736  								Resources:   []string{"*", "a"},
  3737  							},
  3738  						},
  3739  					}},
  3740  				},
  3741  			},
  3742  		},
  3743  		expectedError: `if '*' is present, must not specify other resources without subresources`,
  3744  	}, {
  3745  		name: "resource a/* cannot mix with a/x",
  3746  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3747  			ObjectMeta: metav1.ObjectMeta{
  3748  				Name: "config",
  3749  			},
  3750  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3751  				PolicyName: "xyzlimit-scale.example.com",
  3752  				ParamRef: &admissionregistration.ParamRef{
  3753  					Name:                    "xyzlimit-scale-setting.example.com",
  3754  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3755  				},
  3756  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3757  				MatchResources: &admissionregistration.MatchResources{
  3758  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3759  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3760  							Operations: []admissionregistration.OperationType{"CREATE"},
  3761  							Rule: admissionregistration.Rule{
  3762  								APIGroups:   []string{"a"},
  3763  								APIVersions: []string{"a"},
  3764  								Resources:   []string{"a/*", "a/x"},
  3765  							},
  3766  						},
  3767  					}},
  3768  				},
  3769  			},
  3770  		},
  3771  		expectedError: `spec.matchResouces.resourceRules[0].resources[1]: Invalid value: "a/x": if 'a/*' is present, must not specify a/x`,
  3772  	}, {
  3773  		name: "resource a/* can mix with a",
  3774  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3775  			ObjectMeta: metav1.ObjectMeta{
  3776  				Name: "config",
  3777  			},
  3778  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3779  				PolicyName: "xyzlimit-scale.example.com",
  3780  				ParamRef: &admissionregistration.ParamRef{
  3781  					Name:                    "xyzlimit-scale-setting.example.com",
  3782  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3783  				},
  3784  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3785  				MatchResources: &admissionregistration.MatchResources{
  3786  					NamespaceSelector: &metav1.LabelSelector{
  3787  						MatchLabels: map[string]string{"a": "b"},
  3788  					},
  3789  					ObjectSelector: &metav1.LabelSelector{
  3790  						MatchLabels: map[string]string{"a": "b"},
  3791  					},
  3792  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  3793  						r := admissionregistration.MatchPolicyType("Exact")
  3794  						return &r
  3795  					}(),
  3796  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3797  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3798  							Operations: []admissionregistration.OperationType{"CREATE"},
  3799  							Rule: admissionregistration.Rule{
  3800  								APIGroups:   []string{"a"},
  3801  								APIVersions: []string{"a"},
  3802  								Resources:   []string{"a/*", "a"},
  3803  							},
  3804  						},
  3805  					}},
  3806  				},
  3807  			},
  3808  		},
  3809  	}, {
  3810  		name: "resource */a cannot mix with x/a",
  3811  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3812  			ObjectMeta: metav1.ObjectMeta{
  3813  				Name: "config",
  3814  			},
  3815  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3816  				PolicyName: "xyzlimit-scale.example.com",
  3817  				ParamRef: &admissionregistration.ParamRef{
  3818  					Name:                    "xyzlimit-scale-setting.example.com",
  3819  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3820  				},
  3821  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3822  				MatchResources: &admissionregistration.MatchResources{
  3823  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3824  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3825  							Operations: []admissionregistration.OperationType{"CREATE"},
  3826  							Rule: admissionregistration.Rule{
  3827  								APIGroups:   []string{"a"},
  3828  								APIVersions: []string{"a"},
  3829  								Resources:   []string{"*/a", "x/a"},
  3830  							},
  3831  						},
  3832  					}},
  3833  				},
  3834  			},
  3835  		},
  3836  		expectedError: `spec.matchResouces.resourceRules[0].resources[1]: Invalid value: "x/a": if '*/a' is present, must not specify x/a`,
  3837  	}, {
  3838  		name: "resource */* cannot mix with other resources",
  3839  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3840  			ObjectMeta: metav1.ObjectMeta{
  3841  				Name: "config",
  3842  			},
  3843  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3844  				PolicyName: "xyzlimit-scale.example.com",
  3845  				ParamRef: &admissionregistration.ParamRef{
  3846  					Name:                    "xyzlimit-scale-setting.example.com",
  3847  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3848  				},
  3849  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3850  				MatchResources: &admissionregistration.MatchResources{
  3851  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  3852  						RuleWithOperations: admissionregistration.RuleWithOperations{
  3853  							Operations: []admissionregistration.OperationType{"CREATE"},
  3854  							Rule: admissionregistration.Rule{
  3855  								APIGroups:   []string{"a"},
  3856  								APIVersions: []string{"a"},
  3857  								Resources:   []string{"*/*", "a"},
  3858  							},
  3859  						},
  3860  					}},
  3861  				},
  3862  			},
  3863  		},
  3864  		expectedError: `spec.matchResouces.resourceRules[0].resources: Invalid value: []string{"*/*", "a"}: if '*/*' is present, must not specify other resources`,
  3865  	}, {
  3866  		name: "validationActions must be unique",
  3867  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3868  			ObjectMeta: metav1.ObjectMeta{
  3869  				Name: "config",
  3870  			},
  3871  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3872  				PolicyName: "xyzlimit-scale.example.com",
  3873  				ParamRef: &admissionregistration.ParamRef{
  3874  					Name:                    "xyzlimit-scale-setting.example.com",
  3875  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3876  				},
  3877  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny, admissionregistration.Deny},
  3878  			},
  3879  		},
  3880  		expectedError: `spec.validationActions[1]: Duplicate value: "Deny"`,
  3881  	}, {
  3882  		name: "validationActions must contain supported values",
  3883  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3884  			ObjectMeta: metav1.ObjectMeta{
  3885  				Name: "config",
  3886  			},
  3887  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3888  				PolicyName: "xyzlimit-scale.example.com",
  3889  				ParamRef: &admissionregistration.ParamRef{
  3890  					Name:                    "xyzlimit-scale-setting.example.com",
  3891  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3892  				},
  3893  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.ValidationAction("illegal")},
  3894  			},
  3895  		},
  3896  		expectedError: `Unsupported value: "illegal": supported values: "Audit", "Deny", "Warn"`,
  3897  	}, {
  3898  		name: "paramRef selector must not be set when name is set",
  3899  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3900  			ObjectMeta: metav1.ObjectMeta{
  3901  				Name: "config",
  3902  			},
  3903  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3904  				PolicyName:        "xyzlimit-scale.example.com",
  3905  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3906  				ParamRef: &admissionregistration.ParamRef{
  3907  					Name: "xyzlimit-scale-setting.example.com",
  3908  					Selector: &metav1.LabelSelector{
  3909  						MatchLabels: map[string]string{
  3910  							"label": "value",
  3911  						},
  3912  					},
  3913  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3914  				},
  3915  			},
  3916  		},
  3917  		expectedError: `spec.paramRef.name: Forbidden: name and selector are mutually exclusive, spec.paramRef.selector: Forbidden: name and selector are mutually exclusive`,
  3918  	}, {
  3919  		name: "paramRef parameterNotFoundAction must be set",
  3920  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3921  			ObjectMeta: metav1.ObjectMeta{
  3922  				Name: "config",
  3923  			},
  3924  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3925  				PolicyName:        "xyzlimit-scale.example.com",
  3926  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3927  				ParamRef: &admissionregistration.ParamRef{
  3928  					Name: "xyzlimit-scale-setting.example.com",
  3929  				},
  3930  			},
  3931  		},
  3932  		expectedError: "spec.paramRef.parameterNotFoundAction: Required value",
  3933  	}, {
  3934  		name: "paramRef parameterNotFoundAction must be an valid value",
  3935  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3936  			ObjectMeta: metav1.ObjectMeta{
  3937  				Name: "config",
  3938  			},
  3939  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3940  				PolicyName:        "xyzlimit-scale.example.com",
  3941  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3942  				ParamRef: &admissionregistration.ParamRef{
  3943  					Name:                    "xyzlimit-scale-setting.example.com",
  3944  					ParameterNotFoundAction: ptr(admissionregistration.ParameterNotFoundActionType("invalid")),
  3945  				},
  3946  			},
  3947  		},
  3948  		expectedError: `spec.paramRef.parameterNotFoundAction: Unsupported value: "invalid": supported values: "Deny", "Allow"`,
  3949  	}, {
  3950  		name: "paramRef one of name or selector",
  3951  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3952  			ObjectMeta: metav1.ObjectMeta{
  3953  				Name: "config",
  3954  			},
  3955  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3956  				PolicyName:        "xyzlimit-scale.example.com",
  3957  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  3958  				ParamRef: &admissionregistration.ParamRef{
  3959  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  3960  				},
  3961  			},
  3962  		},
  3963  		expectedError: `one of name or selector must be specified`,
  3964  	}}
  3965  	for _, test := range tests {
  3966  		t.Run(test.name, func(t *testing.T) {
  3967  			errs := ValidateValidatingAdmissionPolicyBinding(test.config)
  3968  			err := errs.ToAggregate()
  3969  			if err != nil {
  3970  				if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  3971  					t.Errorf("expected to contain %s, got %s", e, a)
  3972  				}
  3973  			} else {
  3974  				if test.expectedError != "" {
  3975  					t.Errorf("unexpected no error, expected to contain %s", test.expectedError)
  3976  				}
  3977  			}
  3978  		})
  3979  
  3980  	}
  3981  }
  3982  
  3983  func TestValidateValidatingAdmissionPolicyBindingUpdate(t *testing.T) {
  3984  	tests := []struct {
  3985  		name          string
  3986  		oldconfig     *admissionregistration.ValidatingAdmissionPolicyBinding
  3987  		config        *admissionregistration.ValidatingAdmissionPolicyBinding
  3988  		expectedError string
  3989  	}{{
  3990  		name: "should pass on valid new ValidatingAdmissionPolicyBinding",
  3991  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  3992  			ObjectMeta: metav1.ObjectMeta{
  3993  				Name: "config",
  3994  			},
  3995  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  3996  				PolicyName: "xyzlimit-scale.example.com",
  3997  				ParamRef: &admissionregistration.ParamRef{
  3998  					Name:                    "xyzlimit-scale-setting.example.com",
  3999  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  4000  				},
  4001  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  4002  				MatchResources: &admissionregistration.MatchResources{
  4003  					NamespaceSelector: &metav1.LabelSelector{
  4004  						MatchLabels: map[string]string{"a": "b"},
  4005  					},
  4006  					ObjectSelector: &metav1.LabelSelector{
  4007  						MatchLabels: map[string]string{"a": "b"},
  4008  					},
  4009  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  4010  						r := admissionregistration.MatchPolicyType("Exact")
  4011  						return &r
  4012  					}(),
  4013  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  4014  						RuleWithOperations: admissionregistration.RuleWithOperations{
  4015  							Operations: []admissionregistration.OperationType{"CREATE"},
  4016  							Rule: admissionregistration.Rule{
  4017  								APIGroups:   []string{"a"},
  4018  								APIVersions: []string{"a"},
  4019  								Resources:   []string{"a"},
  4020  							},
  4021  						},
  4022  					}},
  4023  				},
  4024  			},
  4025  		},
  4026  		oldconfig: &admissionregistration.ValidatingAdmissionPolicyBinding{
  4027  			ObjectMeta: metav1.ObjectMeta{
  4028  				Name: "config",
  4029  			},
  4030  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  4031  				PolicyName: "xyzlimit-scale.example.com",
  4032  				ParamRef: &admissionregistration.ParamRef{
  4033  					Name:                    "xyzlimit-scale-setting.example.com",
  4034  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  4035  				},
  4036  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  4037  				MatchResources: &admissionregistration.MatchResources{
  4038  					NamespaceSelector: &metav1.LabelSelector{
  4039  						MatchLabels: map[string]string{"a": "b"},
  4040  					},
  4041  					ObjectSelector: &metav1.LabelSelector{
  4042  						MatchLabels: map[string]string{"a": "b"},
  4043  					},
  4044  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  4045  						r := admissionregistration.MatchPolicyType("Exact")
  4046  						return &r
  4047  					}(),
  4048  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  4049  						RuleWithOperations: admissionregistration.RuleWithOperations{
  4050  							Operations: []admissionregistration.OperationType{"CREATE"},
  4051  							Rule: admissionregistration.Rule{
  4052  								APIGroups:   []string{"a"},
  4053  								APIVersions: []string{"a"},
  4054  								Resources:   []string{"a"},
  4055  							},
  4056  						},
  4057  					}},
  4058  				},
  4059  			},
  4060  		},
  4061  	}, {
  4062  		name: "should pass on valid new ValidatingAdmissionPolicyBinding with invalid old ValidatingAdmissionPolicyBinding",
  4063  		config: &admissionregistration.ValidatingAdmissionPolicyBinding{
  4064  			ObjectMeta: metav1.ObjectMeta{
  4065  				Name: "config",
  4066  			},
  4067  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{
  4068  				PolicyName: "xyzlimit-scale.example.com",
  4069  				ParamRef: &admissionregistration.ParamRef{
  4070  					Name:                    "xyzlimit-scale-setting.example.com",
  4071  					ParameterNotFoundAction: ptr(admissionregistration.DenyAction),
  4072  				},
  4073  				ValidationActions: []admissionregistration.ValidationAction{admissionregistration.Deny},
  4074  				MatchResources: &admissionregistration.MatchResources{
  4075  					NamespaceSelector: &metav1.LabelSelector{
  4076  						MatchLabels: map[string]string{"a": "b"},
  4077  					},
  4078  					ObjectSelector: &metav1.LabelSelector{
  4079  						MatchLabels: map[string]string{"a": "b"},
  4080  					},
  4081  					MatchPolicy: func() *admissionregistration.MatchPolicyType {
  4082  						r := admissionregistration.MatchPolicyType("Exact")
  4083  						return &r
  4084  					}(),
  4085  					ResourceRules: []admissionregistration.NamedRuleWithOperations{{
  4086  						RuleWithOperations: admissionregistration.RuleWithOperations{
  4087  							Operations: []admissionregistration.OperationType{"CREATE"},
  4088  							Rule: admissionregistration.Rule{
  4089  								APIGroups:   []string{"a"},
  4090  								APIVersions: []string{"a"},
  4091  								Resources:   []string{"a"},
  4092  							},
  4093  						},
  4094  					}},
  4095  				},
  4096  			},
  4097  		},
  4098  		oldconfig: &admissionregistration.ValidatingAdmissionPolicyBinding{
  4099  			ObjectMeta: metav1.ObjectMeta{
  4100  				Name: "!!!",
  4101  			},
  4102  			Spec: admissionregistration.ValidatingAdmissionPolicyBindingSpec{},
  4103  		},
  4104  	}}
  4105  	for _, test := range tests {
  4106  		t.Run(test.name, func(t *testing.T) {
  4107  			errs := ValidateValidatingAdmissionPolicyBindingUpdate(test.config, test.oldconfig)
  4108  			err := errs.ToAggregate()
  4109  			if err != nil {
  4110  				if e, a := test.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  4111  					t.Errorf("expected to contain %s, got %s", e, a)
  4112  				}
  4113  			} else {
  4114  				if test.expectedError != "" {
  4115  					t.Errorf("unexpected no error, expected to contain %s", test.expectedError)
  4116  				}
  4117  			}
  4118  		})
  4119  
  4120  	}
  4121  }
  4122  
  4123  func TestValidateValidatingAdmissionPolicyStatus(t *testing.T) {
  4124  	for _, tc := range []struct {
  4125  		name          string
  4126  		status        *admissionregistration.ValidatingAdmissionPolicyStatus
  4127  		expectedError string
  4128  	}{{
  4129  		name:   "empty",
  4130  		status: &admissionregistration.ValidatingAdmissionPolicyStatus{},
  4131  	}, {
  4132  		name: "type checking",
  4133  		status: &admissionregistration.ValidatingAdmissionPolicyStatus{
  4134  			TypeChecking: &admissionregistration.TypeChecking{
  4135  				ExpressionWarnings: []admissionregistration.ExpressionWarning{{
  4136  					FieldRef: "spec.validations[0].expression",
  4137  					Warning:  "message",
  4138  				}},
  4139  			},
  4140  		},
  4141  	}, {
  4142  		name: "type checking bad json path",
  4143  		status: &admissionregistration.ValidatingAdmissionPolicyStatus{
  4144  			TypeChecking: &admissionregistration.TypeChecking{
  4145  				ExpressionWarnings: []admissionregistration.ExpressionWarning{{
  4146  					FieldRef: "spec[foo]",
  4147  					Warning:  "message",
  4148  				}},
  4149  			},
  4150  		},
  4151  		expectedError: "invalid JSONPath: invalid array index foo",
  4152  	}, {
  4153  		name: "type checking missing warning",
  4154  		status: &admissionregistration.ValidatingAdmissionPolicyStatus{
  4155  			TypeChecking: &admissionregistration.TypeChecking{
  4156  				ExpressionWarnings: []admissionregistration.ExpressionWarning{{
  4157  					FieldRef: "spec.validations[0].expression",
  4158  				}},
  4159  			},
  4160  		},
  4161  		expectedError: "Required value",
  4162  	}, {
  4163  		name: "type checking missing fieldRef",
  4164  		status: &admissionregistration.ValidatingAdmissionPolicyStatus{
  4165  			TypeChecking: &admissionregistration.TypeChecking{
  4166  				ExpressionWarnings: []admissionregistration.ExpressionWarning{{
  4167  					Warning: "message",
  4168  				}},
  4169  			},
  4170  		},
  4171  		expectedError: "Required value",
  4172  	},
  4173  	} {
  4174  		t.Run(tc.name, func(t *testing.T) {
  4175  			errs := validateValidatingAdmissionPolicyStatus(tc.status, field.NewPath("status"))
  4176  			err := errs.ToAggregate()
  4177  			if err != nil {
  4178  				if e, a := tc.expectedError, err.Error(); !strings.Contains(a, e) || e == "" {
  4179  					t.Errorf("expected to contain %s, got %s", e, a)
  4180  				}
  4181  			} else {
  4182  				if tc.expectedError != "" {
  4183  					t.Errorf("unexpected no error, expected to contain %s", tc.expectedError)
  4184  				}
  4185  			}
  4186  		})
  4187  	}
  4188  }
  4189  
  4190  func get65MatchConditions() []admissionregistration.MatchCondition {
  4191  	result := []admissionregistration.MatchCondition{}
  4192  	for i := 0; i < 65; i++ {
  4193  		result = append(result, admissionregistration.MatchCondition{
  4194  			Name:       fmt.Sprintf("test%v", i),
  4195  			Expression: "true",
  4196  		})
  4197  	}
  4198  	return result
  4199  }