...

Source file src/k8s.io/kubernetes/pkg/apis/admissionregistration/validation/validation_test.go

Documentation: k8s.io/kubernetes/pkg/apis/admissionregistration/validation

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

View as plain text