Matchconditions admission webhooks alpha implementation for kep-3716 (#116261)
* api changes adding match conditions * feature gate and registry strategy to drop fields * matchConditions logic for admission webhooks * feedback * update test * import order * bears.com * update fail policy ignore behavior * update docs and matcher to hold fail policy as non-pointer * update matcher error aggregation, fix early fail failpolicy ignore, update docs * final cleanup * openapi gen
This commit is contained in:
parent
c072cae4d0
commit
5e5b3029f3
44
api/openapi-spec/swagger.json
generated
44
api/openapi-spec/swagger.json
generated
@ -1,5 +1,23 @@
|
||||
{
|
||||
"definitions": {
|
||||
"io.k8s.api.admissionregistration.v1.MatchCondition": {
|
||||
"description": "MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.",
|
||||
"properties": {
|
||||
"expression": {
|
||||
"description": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"description": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting 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]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"expression"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.admissionregistration.v1.MutatingWebhook": {
|
||||
"description": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.",
|
||||
"properties": {
|
||||
@ -18,6 +36,19 @@
|
||||
"description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.",
|
||||
"type": "string"
|
||||
},
|
||||
"matchConditions": {
|
||||
"description": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchCondition"
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"name"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
},
|
||||
"matchPolicy": {
|
||||
"description": "matchPolicy defines how the \"rules\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.\n\nDefaults to \"Equivalent\"",
|
||||
"type": "string"
|
||||
@ -219,6 +250,19 @@
|
||||
"description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.",
|
||||
"type": "string"
|
||||
},
|
||||
"matchConditions": {
|
||||
"description": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
"items": {
|
||||
"$ref": "#/definitions/io.k8s.api.admissionregistration.v1.MatchCondition"
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"name"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
},
|
||||
"matchPolicy": {
|
||||
"description": "matchPolicy defines how the \"rules\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.\n\nDefaults to \"Equivalent\"",
|
||||
"type": "string"
|
||||
|
@ -1,6 +1,26 @@
|
||||
{
|
||||
"components": {
|
||||
"schemas": {
|
||||
"io.k8s.api.admissionregistration.v1.MatchCondition": {
|
||||
"description": "MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.",
|
||||
"properties": {
|
||||
"expression": {
|
||||
"default": "",
|
||||
"description": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.",
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"default": "",
|
||||
"description": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting 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]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.",
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"required": [
|
||||
"name",
|
||||
"expression"
|
||||
],
|
||||
"type": "object"
|
||||
},
|
||||
"io.k8s.api.admissionregistration.v1.MutatingWebhook": {
|
||||
"description": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.",
|
||||
"properties": {
|
||||
@ -25,6 +45,24 @@
|
||||
"description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.",
|
||||
"type": "string"
|
||||
},
|
||||
"matchConditions": {
|
||||
"description": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MatchCondition"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"name"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
},
|
||||
"matchPolicy": {
|
||||
"description": "matchPolicy defines how the \"rules\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.\n\nDefaults to \"Equivalent\"",
|
||||
"type": "string"
|
||||
@ -272,6 +310,24 @@
|
||||
"description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Fail.",
|
||||
"type": "string"
|
||||
},
|
||||
"matchConditions": {
|
||||
"description": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
"items": {
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "#/components/schemas/io.k8s.api.admissionregistration.v1.MatchCondition"
|
||||
}
|
||||
],
|
||||
"default": {}
|
||||
},
|
||||
"type": "array",
|
||||
"x-kubernetes-list-map-keys": [
|
||||
"name"
|
||||
],
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge"
|
||||
},
|
||||
"matchPolicy": {
|
||||
"description": "matchPolicy defines how the \"rules\" list is used to match incoming requests. Allowed values are \"Exact\" or \"Equivalent\".\n\n- Exact: match a request only if it exactly matches a specified rule. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, but \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would not be sent to the webhook.\n\n- Equivalent: match a request if modifies a resource listed in rules, even via another API group or version. For example, if deployments can be modified via apps/v1, apps/v1beta1, and extensions/v1beta1, and \"rules\" only included `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]`, a request to apps/v1beta1 or extensions/v1beta1 would be converted to apps/v1 and sent to the webhook.\n\nDefaults to \"Equivalent\"",
|
||||
"type": "string"
|
||||
|
@ -740,6 +740,24 @@ type ValidatingWebhook struct {
|
||||
// does not understand, calls to the webhook will fail and be subject to the failure policy.
|
||||
// +optional
|
||||
AdmissionReviewVersions []string
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
MatchConditions []MatchCondition
|
||||
}
|
||||
|
||||
// MutatingWebhook describes an admission webhook and the resources and operations it applies to.
|
||||
@ -882,6 +900,24 @@ type MutatingWebhook struct {
|
||||
// Defaults to "Never".
|
||||
// +optional
|
||||
ReinvocationPolicy *ReinvocationPolicyType
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
MatchConditions []MatchCondition
|
||||
}
|
||||
|
||||
// ReinvocationPolicyType specifies what type of policy the admission hook uses.
|
||||
@ -987,3 +1023,32 @@ type ServiceReference struct {
|
||||
// +optional
|
||||
Port int32
|
||||
}
|
||||
|
||||
// MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.
|
||||
type MatchCondition struct {
|
||||
// Name is an identifier for this match condition, used for strategic merging of MatchConditions,
|
||||
// as well as providing an identifier for logging purposes. A good name should be descriptive of
|
||||
// the associated expression.
|
||||
// Name must be a qualified name consisting 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]') with an
|
||||
// optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')
|
||||
//
|
||||
// Required.
|
||||
Name string
|
||||
|
||||
// Expression represents the expression which will be evaluated by CEL. Must evaluate to bool.
|
||||
// CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:
|
||||
//
|
||||
// 'object' - The object from the incoming request. The value is null for DELETE requests.
|
||||
// 'oldObject' - The existing object. The value is null for CREATE requests.
|
||||
// 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest).
|
||||
// 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
|
||||
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
|
||||
// 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
|
||||
// request resource.
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// Required.
|
||||
Expression string
|
||||
}
|
||||
|
66
pkg/apis/admissionregistration/util.go
Normal file
66
pkg/apis/admissionregistration/util.go
Normal file
@ -0,0 +1,66 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admissionregistration
|
||||
|
||||
import (
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
)
|
||||
|
||||
// DropDisabledMutatingWebhookConfigurationFields removes disabled fields from the mutatingWebhookConfiguration metadata and spec.
|
||||
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a mutatingWebhookConfiguration
|
||||
func DropDisabledMutatingWebhookConfigurationFields(mutatingWebhookConfiguration, oldMutatingWebhookConfiguration *MutatingWebhookConfiguration) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AdmissionWebhookMatchConditions) && !matchConditionsInUseMutatingWebhook(oldMutatingWebhookConfiguration) {
|
||||
for i := range mutatingWebhookConfiguration.Webhooks {
|
||||
mutatingWebhookConfiguration.Webhooks[i].MatchConditions = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func matchConditionsInUseMutatingWebhook(mutatingWebhookConfiguration *MutatingWebhookConfiguration) bool {
|
||||
if mutatingWebhookConfiguration == nil {
|
||||
return false
|
||||
}
|
||||
for _, webhook := range mutatingWebhookConfiguration.Webhooks {
|
||||
if len(webhook.MatchConditions) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// DropDisabledValidatingWebhookConfigurationFields removes disabled fields from the validatingWebhookConfiguration metadata and spec.
|
||||
// This should be called from PrepareForCreate/PrepareForUpdate for all resources containing a validatingWebhookConfiguration
|
||||
func DropDisabledValidatingWebhookConfigurationFields(validatingWebhookConfiguration, oldValidatingWebhookConfiguration *ValidatingWebhookConfiguration) {
|
||||
if !utilfeature.DefaultFeatureGate.Enabled(genericfeatures.AdmissionWebhookMatchConditions) && !matchConditionsInUseValidatingWebhook(oldValidatingWebhookConfiguration) {
|
||||
for i := range validatingWebhookConfiguration.Webhooks {
|
||||
validatingWebhookConfiguration.Webhooks[i].MatchConditions = nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func matchConditionsInUseValidatingWebhook(validatingWebhookConfiguration *ValidatingWebhookConfiguration) bool {
|
||||
if validatingWebhookConfiguration == nil {
|
||||
return false
|
||||
}
|
||||
for _, webhook := range validatingWebhookConfiguration.Webhooks {
|
||||
if len(webhook.MatchConditions) != 0 {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
417
pkg/apis/admissionregistration/util_test.go
Normal file
417
pkg/apis/admissionregistration/util_test.go
Normal file
@ -0,0 +1,417 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admissionregistration
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
)
|
||||
|
||||
func TestDropDisabledMutatingWebhookConfigurationFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
old *MutatingWebhookConfiguration
|
||||
new *MutatingWebhookConfiguration
|
||||
featureGateEnabled bool
|
||||
expected []MatchCondition
|
||||
}{
|
||||
{
|
||||
name: "create with no match conditions, feature gate off",
|
||||
old: nil,
|
||||
new: &MutatingWebhookConfiguration{
|
||||
Webhooks: []MutatingWebhook{
|
||||
{},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: false,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "create with match conditions, feature gate off",
|
||||
old: nil,
|
||||
new: &MutatingWebhookConfiguration{
|
||||
Webhooks: []MutatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: false,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "create with no match conditions, feature gate on",
|
||||
old: nil,
|
||||
new: &MutatingWebhookConfiguration{
|
||||
Webhooks: []MutatingWebhook{
|
||||
{},
|
||||
{},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: true,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "create with match conditions, feature gate on",
|
||||
old: nil,
|
||||
new: &MutatingWebhookConfiguration{
|
||||
Webhooks: []MutatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: true,
|
||||
expected: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update with old has match conditions feature gate on",
|
||||
old: &MutatingWebhookConfiguration{
|
||||
Webhooks: []MutatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: &MutatingWebhookConfiguration{
|
||||
Webhooks: []MutatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: true,
|
||||
expected: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update with old has match conditions feature gate off",
|
||||
old: &MutatingWebhookConfiguration{
|
||||
Webhooks: []MutatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: &MutatingWebhookConfiguration{
|
||||
Webhooks: []MutatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: false,
|
||||
expected: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AdmissionWebhookMatchConditions, test.featureGateEnabled)()
|
||||
DropDisabledMutatingWebhookConfigurationFields(test.new, test.old)
|
||||
|
||||
for _, hook := range test.new.Webhooks {
|
||||
if test.expected == nil {
|
||||
if hook.MatchConditions != nil {
|
||||
t.Error("expected all hooks matchConditions to be nil")
|
||||
}
|
||||
} else {
|
||||
require.Equal(t, len(test.expected), len(hook.MatchConditions))
|
||||
for i, matchCondition := range hook.MatchConditions {
|
||||
require.Equal(t, test.expected[i], matchCondition)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestDropDisabledValidatingWebhookConfigurationFields(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
old *ValidatingWebhookConfiguration
|
||||
new *ValidatingWebhookConfiguration
|
||||
featureGateEnabled bool
|
||||
expected []MatchCondition
|
||||
}{
|
||||
{
|
||||
name: "create with no match conditions, feature gate off",
|
||||
old: nil,
|
||||
new: &ValidatingWebhookConfiguration{
|
||||
Webhooks: []ValidatingWebhook{
|
||||
{},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: false,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "create with match conditions, feature gate off",
|
||||
old: nil,
|
||||
new: &ValidatingWebhookConfiguration{
|
||||
Webhooks: []ValidatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: false,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "create with no match conditions, feature gate on",
|
||||
old: nil,
|
||||
new: &ValidatingWebhookConfiguration{
|
||||
Webhooks: []ValidatingWebhook{
|
||||
{},
|
||||
{},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: true,
|
||||
expected: nil,
|
||||
},
|
||||
{
|
||||
name: "create with match conditions, feature gate on",
|
||||
old: nil,
|
||||
new: &ValidatingWebhookConfiguration{
|
||||
Webhooks: []ValidatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: true,
|
||||
expected: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update with old has match conditions feature gate on",
|
||||
old: &ValidatingWebhookConfiguration{
|
||||
Webhooks: []ValidatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: &ValidatingWebhookConfiguration{
|
||||
Webhooks: []ValidatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: true,
|
||||
expected: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "update with old has match conditions feature gate off",
|
||||
old: &ValidatingWebhookConfiguration{
|
||||
Webhooks: []ValidatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
new: &ValidatingWebhookConfiguration{
|
||||
Webhooks: []ValidatingWebhook{
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
MatchConditions: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
featureGateEnabled: false,
|
||||
expected: []MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AdmissionWebhookMatchConditions, test.featureGateEnabled)()
|
||||
DropDisabledValidatingWebhookConfigurationFields(test.new, test.old)
|
||||
|
||||
for _, hook := range test.new.Webhooks {
|
||||
if test.expected == nil {
|
||||
if hook.MatchConditions != nil {
|
||||
t.Error("expected all hooks matchConditions to be nil")
|
||||
}
|
||||
} else {
|
||||
require.Equal(t, len(test.expected), len(hook.MatchConditions))
|
||||
for i, matchCondition := range hook.MatchConditions {
|
||||
require.Equal(t, test.expected[i], matchCondition)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -38,6 +38,16 @@ func init() {
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(s *runtime.Scheme) error {
|
||||
if err := s.AddGeneratedConversionFunc((*v1.MatchCondition)(nil), (*admissionregistration.MatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_MatchCondition_To_admissionregistration_MatchCondition(a.(*v1.MatchCondition), b.(*admissionregistration.MatchCondition), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*admissionregistration.MatchCondition)(nil), (*v1.MatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_admissionregistration_MatchCondition_To_v1_MatchCondition(a.(*admissionregistration.MatchCondition), b.(*v1.MatchCondition), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1.MutatingWebhook)(nil), (*admissionregistration.MutatingWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1_MutatingWebhook_To_admissionregistration_MutatingWebhook(a.(*v1.MutatingWebhook), b.(*admissionregistration.MutatingWebhook), scope)
|
||||
}); err != nil {
|
||||
@ -141,6 +151,28 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1_MatchCondition_To_admissionregistration_MatchCondition(in *v1.MatchCondition, out *admissionregistration.MatchCondition, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Expression = in.Expression
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1_MatchCondition_To_admissionregistration_MatchCondition is an autogenerated conversion function.
|
||||
func Convert_v1_MatchCondition_To_admissionregistration_MatchCondition(in *v1.MatchCondition, out *admissionregistration.MatchCondition, s conversion.Scope) error {
|
||||
return autoConvert_v1_MatchCondition_To_admissionregistration_MatchCondition(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_admissionregistration_MatchCondition_To_v1_MatchCondition(in *admissionregistration.MatchCondition, out *v1.MatchCondition, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Expression = in.Expression
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_admissionregistration_MatchCondition_To_v1_MatchCondition is an autogenerated conversion function.
|
||||
func Convert_admissionregistration_MatchCondition_To_v1_MatchCondition(in *admissionregistration.MatchCondition, out *v1.MatchCondition, s conversion.Scope) error {
|
||||
return autoConvert_admissionregistration_MatchCondition_To_v1_MatchCondition(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1_MutatingWebhook_To_admissionregistration_MutatingWebhook(in *v1.MutatingWebhook, out *admissionregistration.MutatingWebhook, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
if err := Convert_v1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil {
|
||||
@ -165,6 +197,7 @@ func autoConvert_v1_MutatingWebhook_To_admissionregistration_MutatingWebhook(in
|
||||
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
|
||||
out.ReinvocationPolicy = (*admissionregistration.ReinvocationPolicyType)(unsafe.Pointer(in.ReinvocationPolicy))
|
||||
out.MatchConditions = *(*[]admissionregistration.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -197,6 +230,7 @@ func autoConvert_admissionregistration_MutatingWebhook_To_v1_MutatingWebhook(in
|
||||
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
|
||||
out.ReinvocationPolicy = (*v1.ReinvocationPolicyType)(unsafe.Pointer(in.ReinvocationPolicy))
|
||||
out.MatchConditions = *(*[]v1.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -374,6 +408,7 @@ func autoConvert_v1_ValidatingWebhook_To_admissionregistration_ValidatingWebhook
|
||||
out.SideEffects = (*admissionregistration.SideEffectClass)(unsafe.Pointer(in.SideEffects))
|
||||
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
|
||||
out.MatchConditions = *(*[]admissionregistration.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -405,6 +440,7 @@ func autoConvert_admissionregistration_ValidatingWebhook_To_v1_ValidatingWebhook
|
||||
out.SideEffects = (*v1.SideEffectClass)(unsafe.Pointer(in.SideEffects))
|
||||
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
|
||||
out.MatchConditions = *(*[]v1.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -40,6 +40,16 @@ func init() {
|
||||
// RegisterConversions adds conversion functions to the given scheme.
|
||||
// Public to allow building arbitrary schemes.
|
||||
func RegisterConversions(s *runtime.Scheme) error {
|
||||
if err := s.AddGeneratedConversionFunc((*v1beta1.MatchCondition)(nil), (*admissionregistration.MatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_MatchCondition_To_admissionregistration_MatchCondition(a.(*v1beta1.MatchCondition), b.(*admissionregistration.MatchCondition), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*admissionregistration.MatchCondition)(nil), (*v1beta1.MatchCondition)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_admissionregistration_MatchCondition_To_v1beta1_MatchCondition(a.(*admissionregistration.MatchCondition), b.(*v1beta1.MatchCondition), scope)
|
||||
}); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := s.AddGeneratedConversionFunc((*v1beta1.MutatingWebhook)(nil), (*admissionregistration.MutatingWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error {
|
||||
return Convert_v1beta1_MutatingWebhook_To_admissionregistration_MutatingWebhook(a.(*v1beta1.MutatingWebhook), b.(*admissionregistration.MutatingWebhook), scope)
|
||||
}); err != nil {
|
||||
@ -123,6 +133,28 @@ func RegisterConversions(s *runtime.Scheme) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_MatchCondition_To_admissionregistration_MatchCondition(in *v1beta1.MatchCondition, out *admissionregistration.MatchCondition, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Expression = in.Expression
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_v1beta1_MatchCondition_To_admissionregistration_MatchCondition is an autogenerated conversion function.
|
||||
func Convert_v1beta1_MatchCondition_To_admissionregistration_MatchCondition(in *v1beta1.MatchCondition, out *admissionregistration.MatchCondition, s conversion.Scope) error {
|
||||
return autoConvert_v1beta1_MatchCondition_To_admissionregistration_MatchCondition(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_admissionregistration_MatchCondition_To_v1beta1_MatchCondition(in *admissionregistration.MatchCondition, out *v1beta1.MatchCondition, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
out.Expression = in.Expression
|
||||
return nil
|
||||
}
|
||||
|
||||
// Convert_admissionregistration_MatchCondition_To_v1beta1_MatchCondition is an autogenerated conversion function.
|
||||
func Convert_admissionregistration_MatchCondition_To_v1beta1_MatchCondition(in *admissionregistration.MatchCondition, out *v1beta1.MatchCondition, s conversion.Scope) error {
|
||||
return autoConvert_admissionregistration_MatchCondition_To_v1beta1_MatchCondition(in, out, s)
|
||||
}
|
||||
|
||||
func autoConvert_v1beta1_MutatingWebhook_To_admissionregistration_MutatingWebhook(in *v1beta1.MutatingWebhook, out *admissionregistration.MutatingWebhook, s conversion.Scope) error {
|
||||
out.Name = in.Name
|
||||
if err := Convert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil {
|
||||
@ -147,6 +179,7 @@ func autoConvert_v1beta1_MutatingWebhook_To_admissionregistration_MutatingWebhoo
|
||||
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
|
||||
out.ReinvocationPolicy = (*admissionregistration.ReinvocationPolicyType)(unsafe.Pointer(in.ReinvocationPolicy))
|
||||
out.MatchConditions = *(*[]admissionregistration.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -179,6 +212,7 @@ func autoConvert_admissionregistration_MutatingWebhook_To_v1beta1_MutatingWebhoo
|
||||
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
|
||||
out.ReinvocationPolicy = (*v1beta1.ReinvocationPolicyType)(unsafe.Pointer(in.ReinvocationPolicy))
|
||||
out.MatchConditions = *(*[]v1beta1.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -324,6 +358,7 @@ func autoConvert_v1beta1_ValidatingWebhook_To_admissionregistration_ValidatingWe
|
||||
out.SideEffects = (*admissionregistration.SideEffectClass)(unsafe.Pointer(in.SideEffects))
|
||||
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
|
||||
out.MatchConditions = *(*[]admissionregistration.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -355,6 +390,7 @@ func autoConvert_admissionregistration_ValidatingWebhook_To_v1beta1_ValidatingWe
|
||||
out.SideEffects = (*v1beta1.SideEffectClass)(unsafe.Pointer(in.SideEffects))
|
||||
out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds))
|
||||
out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions))
|
||||
out.MatchConditions = *(*[]v1beta1.MatchCondition)(unsafe.Pointer(&in.MatchConditions))
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -18,6 +18,7 @@ package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"reflect"
|
||||
"regexp"
|
||||
"strings"
|
||||
|
||||
@ -34,7 +35,6 @@ import (
|
||||
"k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/util/jsonpath"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||
admissionregistrationv1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1"
|
||||
admissionregistrationv1beta1 "k8s.io/kubernetes/pkg/apis/admissionregistration/v1beta1"
|
||||
@ -212,6 +212,7 @@ func validateAdmissionReviewVersions(versions []string, requireRecognizedAdmissi
|
||||
// ValidateValidatingWebhookConfiguration validates a webhook before creation.
|
||||
func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
|
||||
return validateValidatingWebhookConfiguration(e, validationOptions{
|
||||
ignoreMatchConditions: false,
|
||||
requireNoSideEffects: true,
|
||||
requireRecognizedAdmissionReviewVersion: true,
|
||||
requireUniqueWebhookNames: true,
|
||||
@ -239,6 +240,7 @@ func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingW
|
||||
// ValidateMutatingWebhookConfiguration validates a webhook before creation.
|
||||
func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
|
||||
return validateMutatingWebhookConfiguration(e, validationOptions{
|
||||
ignoreMatchConditions: false,
|
||||
requireNoSideEffects: true,
|
||||
requireRecognizedAdmissionReviewVersion: true,
|
||||
requireUniqueWebhookNames: true,
|
||||
@ -247,6 +249,7 @@ func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebho
|
||||
}
|
||||
|
||||
type validationOptions struct {
|
||||
ignoreMatchConditions bool
|
||||
requireNoSideEffects bool
|
||||
requireRecognizedAdmissionReviewVersion bool
|
||||
requireUniqueWebhookNames bool
|
||||
@ -319,6 +322,11 @@ func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, op
|
||||
case cc.Service != nil:
|
||||
allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
|
||||
}
|
||||
|
||||
if !opts.ignoreMatchConditions {
|
||||
allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, fldPath.Child("matchConditions"))...)
|
||||
}
|
||||
|
||||
return allErrors
|
||||
}
|
||||
|
||||
@ -372,6 +380,11 @@ func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, opts v
|
||||
case cc.Service != nil:
|
||||
allErrors = append(allErrors, webhook.ValidateWebhookService(fldPath.Child("clientConfig").Child("service"), cc.Service.Name, cc.Service.Namespace, cc.Service.Path, cc.Service.Port)...)
|
||||
}
|
||||
|
||||
if !opts.ignoreMatchConditions {
|
||||
allErrors = append(allErrors, validateMatchConditions(hook.MatchConditions, fldPath.Child("matchConditions"))...)
|
||||
}
|
||||
|
||||
return allErrors
|
||||
}
|
||||
|
||||
@ -479,6 +492,34 @@ func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistrati
|
||||
return true
|
||||
}
|
||||
|
||||
// ignoreMatchConditions returns false if any change to match conditions
|
||||
func ignoreMutatingWebhookMatchConditions(new, old []admissionregistration.MutatingWebhook) bool {
|
||||
if len(new) != len(old) {
|
||||
return false
|
||||
}
|
||||
for i := range old {
|
||||
if !reflect.DeepEqual(new[i].MatchConditions, old[i].MatchConditions) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// ignoreMatchConditions returns true if any new expressions are added
|
||||
func ignoreValidatingWebhookMatchConditions(new, old []admissionregistration.ValidatingWebhook) bool {
|
||||
if len(new) != len(old) {
|
||||
return false
|
||||
}
|
||||
for i := range old {
|
||||
if !reflect.DeepEqual(new[i].MatchConditions, old[i].MatchConditions) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
// mutatingHasUniqueWebhookNames returns true if all webhooks have unique names
|
||||
func mutatingHasUniqueWebhookNames(webhooks []admissionregistration.MutatingWebhook) bool {
|
||||
names := sets.NewString()
|
||||
@ -568,6 +609,7 @@ func mutatingWebhookHasInvalidLabelValueInSelector(webhooks []admissionregistrat
|
||||
// ValidateValidatingWebhookConfigurationUpdate validates update of validating webhook configuration
|
||||
func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList {
|
||||
return validateValidatingWebhookConfiguration(newC, validationOptions{
|
||||
ignoreMatchConditions: ignoreValidatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks),
|
||||
requireNoSideEffects: validatingHasNoSideEffects(oldC.Webhooks),
|
||||
requireRecognizedAdmissionReviewVersion: validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
|
||||
requireUniqueWebhookNames: validatingHasUniqueWebhookNames(oldC.Webhooks),
|
||||
@ -578,6 +620,7 @@ func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistrat
|
||||
// ValidateMutatingWebhookConfigurationUpdate validates update of mutating webhook configuration
|
||||
func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList {
|
||||
return validateMutatingWebhookConfiguration(newC, validationOptions{
|
||||
ignoreMatchConditions: ignoreMutatingWebhookMatchConditions(newC.Webhooks, oldC.Webhooks),
|
||||
requireNoSideEffects: mutatingHasNoSideEffects(oldC.Webhooks),
|
||||
requireRecognizedAdmissionReviewVersion: mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks),
|
||||
requireUniqueWebhookNames: mutatingHasUniqueWebhookNames(oldC.Webhooks),
|
||||
@ -779,6 +822,44 @@ func validateNamedRuleWithOperations(n *admissionregistration.NamedRuleWithOpera
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func validateMatchConditions(m []admissionregistration.MatchCondition, fldPath *field.Path) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
conditionNames := sets.NewString()
|
||||
if len(m) > 64 {
|
||||
allErrors = append(allErrors, field.TooMany(fldPath, len(m), 64))
|
||||
}
|
||||
for i, matchCondition := range m {
|
||||
allErrors = append(allErrors, validateMatchCondition(&matchCondition, fldPath.Index(i))...)
|
||||
if len(matchCondition.Name) > 0 {
|
||||
if conditionNames.Has(matchCondition.Name) {
|
||||
allErrors = append(allErrors, field.Duplicate(fldPath.Index(i).Child("name"), matchCondition.Name))
|
||||
} else {
|
||||
conditionNames.Insert(matchCondition.Name)
|
||||
}
|
||||
}
|
||||
}
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func validateMatchCondition(v *admissionregistration.MatchCondition, fldPath *field.Path) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
trimmedExpression := strings.TrimSpace(v.Expression)
|
||||
if len(trimmedExpression) == 0 {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("expression"), ""))
|
||||
} else {
|
||||
allErrors = append(allErrors, validateCELExpression(trimmedExpression, plugincel.OptionalVariableDeclarations{
|
||||
HasParams: false,
|
||||
HasAuthorizer: true,
|
||||
}, fldPath.Child("expression"))...)
|
||||
}
|
||||
if len(v.Name) == 0 {
|
||||
allErrors = append(allErrors, field.Required(fldPath.Child("name"), ""))
|
||||
} else {
|
||||
allErrors = append(allErrors, apivalidation.ValidateQualifiedName(v.Name, fldPath.Child("name"))...)
|
||||
}
|
||||
return allErrors
|
||||
}
|
||||
|
||||
func validateValidation(v *admissionregistration.Validation, paramKind *admissionregistration.ParamKind, fldPath *field.Path) field.ErrorList {
|
||||
var allErrors field.ErrorList
|
||||
trimmedExpression := strings.TrimSpace(v.Expression)
|
||||
|
@ -17,12 +17,12 @@ limitations under the License.
|
||||
package validation
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/util/validation/field"
|
||||
|
||||
"k8s.io/kubernetes/pkg/apis/admissionregistration"
|
||||
)
|
||||
|
||||
@ -752,6 +752,229 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) {
|
||||
},
|
||||
}, true),
|
||||
},
|
||||
{
|
||||
name: "single match condition must have a name",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].name: Required value`,
|
||||
},
|
||||
{
|
||||
name: "all match conditions must have a name",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].name: Required value, webhooks[0].matchConditions[1].name: Required value, webhooks[1].matchConditions[0].name: Required value`,
|
||||
},
|
||||
{
|
||||
name: "single match condition must have a qualified name",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "-hello",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
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]')`,
|
||||
},
|
||||
{
|
||||
name: "all match conditions must have qualified names",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: ".io",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "thing.test.com",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "webhook2.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "some name",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
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]')]`,
|
||||
},
|
||||
{
|
||||
name: "expression is required",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].expression: Required value`,
|
||||
},
|
||||
{
|
||||
name: "expression is required to have some value",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].expression: Required value`,
|
||||
},
|
||||
{
|
||||
name: "invalid expression",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "object.x in [1, 2, ",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "object.x in [1, 2,": compilation failed: ERROR: <input>:1:19: Syntax error: missing ']' at '<EOF>'`,
|
||||
},
|
||||
{
|
||||
name: "unique names same hook",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `matchConditions[1].name: Duplicate value: "webhook.k8s.io"`,
|
||||
},
|
||||
{
|
||||
name: "repeat names allowed across different hooks",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "webhook2.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: ``,
|
||||
},
|
||||
{
|
||||
name: "must evaluate to bool",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "6",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "6": must evaluate to bool`,
|
||||
},
|
||||
{
|
||||
name: "max of 64 match conditions",
|
||||
config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: get65MatchConditions(),
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions: Too many: 65: must have at most 64 items`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@ -1652,6 +1875,229 @@ func TestValidateMutatingWebhookConfiguration(t *testing.T) {
|
||||
},
|
||||
}, true),
|
||||
},
|
||||
{
|
||||
name: "single match condition must have a name",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].name: Required value`,
|
||||
},
|
||||
{
|
||||
name: "all match conditions must have a name",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].name: Required value, webhooks[0].matchConditions[1].name: Required value, webhooks[1].matchConditions[0].name: Required value`,
|
||||
},
|
||||
{
|
||||
name: "single match condition must have a qualified name",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "-hello",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
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]')`,
|
||||
},
|
||||
{
|
||||
name: "all match conditions must have qualified names",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: ".io",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "thing.test.com",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "webhook2.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "some name",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
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]')]`,
|
||||
},
|
||||
{
|
||||
name: "expression is required",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].expression: Required value`,
|
||||
},
|
||||
{
|
||||
name: "expression is required to have some value",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].expression: Required value`,
|
||||
},
|
||||
{
|
||||
name: "invalid expression",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "object.x in [1, 2, ",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "object.x in [1, 2,": compilation failed: ERROR: <input>:1:19: Syntax error: missing ']' at '<EOF>'`,
|
||||
},
|
||||
{
|
||||
name: "unique names same hook",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `matchConditions[1].name: Duplicate value: "webhook.k8s.io"`,
|
||||
},
|
||||
{
|
||||
name: "repeat names allowed across different hooks",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "webhook2.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "true",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: ``,
|
||||
},
|
||||
{
|
||||
name: "must evaluate to bool",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: []admissionregistration.MatchCondition{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
Expression: "6",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions[0].expression: Invalid value: "6": must evaluate to bool`,
|
||||
},
|
||||
{
|
||||
name: "max of 64 match conditions",
|
||||
config: newMutatingWebhookConfiguration([]admissionregistration.MutatingWebhook{
|
||||
{
|
||||
Name: "webhook.k8s.io",
|
||||
ClientConfig: validClientConfig,
|
||||
SideEffects: &noSideEffect,
|
||||
MatchConditions: get65MatchConditions(),
|
||||
},
|
||||
}, true),
|
||||
expectedError: `webhooks[0].matchConditions: Too many: 65: must have at most 64 items`,
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
t.Run(test.name, func(t *testing.T) {
|
||||
@ -3534,3 +3980,14 @@ func TestValidateValidatingAdmissionPolicyStatus(t *testing.T) {
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func get65MatchConditions() []admissionregistration.MatchCondition {
|
||||
result := []admissionregistration.MatchCondition{}
|
||||
for i := 0; i < 65; i++ {
|
||||
result = append(result, admissionregistration.MatchCondition{
|
||||
Name: fmt.Sprintf("test%v", i),
|
||||
Expression: "true",
|
||||
})
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
@ -58,6 +58,22 @@ func (in *ExpressionWarning) DeepCopy() *ExpressionWarning {
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MatchCondition) DeepCopyInto(out *MatchCondition) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchCondition.
|
||||
func (in *MatchCondition) DeepCopy() *MatchCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MatchCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MatchResources) DeepCopyInto(out *MatchResources) {
|
||||
*out = *in
|
||||
@ -154,6 +170,11 @@ func (in *MutatingWebhook) DeepCopyInto(out *MutatingWebhook) {
|
||||
*out = new(ReinvocationPolicyType)
|
||||
**out = **in
|
||||
}
|
||||
if in.MatchConditions != nil {
|
||||
in, out := &in.MatchConditions, &out.MatchConditions
|
||||
*out = make([]MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -656,6 +677,11 @@ func (in *ValidatingWebhook) DeepCopyInto(out *ValidatingWebhook) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.MatchConditions != nil {
|
||||
in, out := &in.MatchConditions, &out.MatchConditions
|
||||
*out = make([]MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -1149,6 +1149,8 @@ var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureS
|
||||
// inherited features from generic apiserver, relisted here to get a conflict if it is changed
|
||||
// unintentionally on either side:
|
||||
|
||||
genericfeatures.AdmissionWebhookMatchConditions: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
genericfeatures.AggregatedDiscoveryEndpoint: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
||||
genericfeatures.APIListChunking: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
166
pkg/generated/openapi/zz_generated.openapi.go
generated
166
pkg/generated/openapi/zz_generated.openapi.go
generated
@ -35,6 +35,7 @@ import (
|
||||
|
||||
func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenAPIDefinition {
|
||||
return map[string]common.OpenAPIDefinition{
|
||||
"k8s.io/api/admissionregistration/v1.MatchCondition": schema_k8sio_api_admissionregistration_v1_MatchCondition(ref),
|
||||
"k8s.io/api/admissionregistration/v1.MutatingWebhook": schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref),
|
||||
"k8s.io/api/admissionregistration/v1.MutatingWebhookConfiguration": schema_k8sio_api_admissionregistration_v1_MutatingWebhookConfiguration(ref),
|
||||
"k8s.io/api/admissionregistration/v1.MutatingWebhookConfigurationList": schema_k8sio_api_admissionregistration_v1_MutatingWebhookConfigurationList(ref),
|
||||
@ -60,6 +61,7 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
"k8s.io/api/admissionregistration/v1alpha1.ValidatingAdmissionPolicySpec": schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicySpec(ref),
|
||||
"k8s.io/api/admissionregistration/v1alpha1.ValidatingAdmissionPolicyStatus": schema_k8sio_api_admissionregistration_v1alpha1_ValidatingAdmissionPolicyStatus(ref),
|
||||
"k8s.io/api/admissionregistration/v1alpha1.Validation": schema_k8sio_api_admissionregistration_v1alpha1_Validation(ref),
|
||||
"k8s.io/api/admissionregistration/v1beta1.MatchCondition": schema_k8sio_api_admissionregistration_v1beta1_MatchCondition(ref),
|
||||
"k8s.io/api/admissionregistration/v1beta1.MutatingWebhook": schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref),
|
||||
"k8s.io/api/admissionregistration/v1beta1.MutatingWebhookConfiguration": schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhookConfiguration(ref),
|
||||
"k8s.io/api/admissionregistration/v1beta1.MutatingWebhookConfigurationList": schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhookConfigurationList(ref),
|
||||
@ -1181,6 +1183,36 @@ func GetOpenAPIDefinitions(ref common.ReferenceCallback) map[string]common.OpenA
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_admissionregistration_v1_MatchCondition(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting 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]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"expression": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "expression"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@ -1283,12 +1315,36 @@ func schema_k8sio_api_admissionregistration_v1_MutatingWebhook(ref common.Refere
|
||||
Enum: []interface{}{"IfNeeded", "Never"},
|
||||
},
|
||||
},
|
||||
"matchConditions": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-map-keys": []interface{}{
|
||||
"name",
|
||||
},
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/admissionregistration/v1.MatchCondition"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "clientConfig", "sideEffects", "admissionReviewVersions"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/admissionregistration/v1.RuleWithOperations", "k8s.io/api/admissionregistration/v1.WebhookClientConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||
"k8s.io/api/admissionregistration/v1.MatchCondition", "k8s.io/api/admissionregistration/v1.RuleWithOperations", "k8s.io/api/admissionregistration/v1.WebhookClientConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||
}
|
||||
}
|
||||
|
||||
@ -1717,12 +1773,36 @@ func schema_k8sio_api_admissionregistration_v1_ValidatingWebhook(ref common.Refe
|
||||
},
|
||||
},
|
||||
},
|
||||
"matchConditions": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-map-keys": []interface{}{
|
||||
"name",
|
||||
},
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/admissionregistration/v1.MatchCondition"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "clientConfig", "sideEffects", "admissionReviewVersions"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/admissionregistration/v1.RuleWithOperations", "k8s.io/api/admissionregistration/v1.WebhookClientConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||
"k8s.io/api/admissionregistration/v1.MatchCondition", "k8s.io/api/admissionregistration/v1.RuleWithOperations", "k8s.io/api/admissionregistration/v1.WebhookClientConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||
}
|
||||
}
|
||||
|
||||
@ -2642,6 +2722,36 @@ func schema_k8sio_api_admissionregistration_v1alpha1_Validation(ref common.Refer
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_admissionregistration_v1beta1_MatchCondition(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook.",
|
||||
Type: []string{"object"},
|
||||
Properties: map[string]spec.Schema{
|
||||
"name": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting 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]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"expression": {
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.",
|
||||
Default: "",
|
||||
Type: []string{"string"},
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "expression"},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.ReferenceCallback) common.OpenAPIDefinition {
|
||||
return common.OpenAPIDefinition{
|
||||
Schema: spec.Schema{
|
||||
@ -2740,12 +2850,36 @@ func schema_k8sio_api_admissionregistration_v1beta1_MutatingWebhook(ref common.R
|
||||
Format: "",
|
||||
},
|
||||
},
|
||||
"matchConditions": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-map-keys": []interface{}{
|
||||
"name",
|
||||
},
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/admissionregistration/v1beta1.MatchCondition"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "clientConfig"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/admissionregistration/v1.RuleWithOperations", "k8s.io/api/admissionregistration/v1beta1.WebhookClientConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||
"k8s.io/api/admissionregistration/v1.RuleWithOperations", "k8s.io/api/admissionregistration/v1beta1.MatchCondition", "k8s.io/api/admissionregistration/v1beta1.WebhookClientConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||
}
|
||||
}
|
||||
|
||||
@ -2991,12 +3125,36 @@ func schema_k8sio_api_admissionregistration_v1beta1_ValidatingWebhook(ref common
|
||||
},
|
||||
},
|
||||
},
|
||||
"matchConditions": {
|
||||
VendorExtensible: spec.VendorExtensible{
|
||||
Extensions: spec.Extensions{
|
||||
"x-kubernetes-list-map-keys": []interface{}{
|
||||
"name",
|
||||
},
|
||||
"x-kubernetes-list-type": "map",
|
||||
"x-kubernetes-patch-merge-key": "name",
|
||||
"x-kubernetes-patch-strategy": "merge",
|
||||
},
|
||||
},
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Description: "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
Type: []string{"array"},
|
||||
Items: &spec.SchemaOrArray{
|
||||
Schema: &spec.Schema{
|
||||
SchemaProps: spec.SchemaProps{
|
||||
Default: map[string]interface{}{},
|
||||
Ref: ref("k8s.io/api/admissionregistration/v1beta1.MatchCondition"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Required: []string{"name", "clientConfig"},
|
||||
},
|
||||
},
|
||||
Dependencies: []string{
|
||||
"k8s.io/api/admissionregistration/v1.RuleWithOperations", "k8s.io/api/admissionregistration/v1beta1.WebhookClientConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||
"k8s.io/api/admissionregistration/v1.RuleWithOperations", "k8s.io/api/admissionregistration/v1beta1.MatchCondition", "k8s.io/api/admissionregistration/v1beta1.WebhookClientConfig", "k8s.io/apimachinery/pkg/apis/meta/v1.LabelSelector"},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -46,6 +46,8 @@ func (mutatingWebhookConfigurationStrategy) NamespaceScoped() bool {
|
||||
func (mutatingWebhookConfigurationStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
ic := obj.(*admissionregistration.MutatingWebhookConfiguration)
|
||||
ic.Generation = 1
|
||||
|
||||
admissionregistration.DropDisabledMutatingWebhookConfigurationFields(ic, nil)
|
||||
}
|
||||
|
||||
// WarningsOnCreate returns warnings for the creation of the given object.
|
||||
@ -58,6 +60,7 @@ func (mutatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Context
|
||||
newIC := obj.(*admissionregistration.MutatingWebhookConfiguration)
|
||||
oldIC := old.(*admissionregistration.MutatingWebhookConfiguration)
|
||||
|
||||
admissionregistration.DropDisabledMutatingWebhookConfigurationFields(newIC, oldIC)
|
||||
// Any changes to the spec increment the generation number, any changes to the
|
||||
// status should reflect the generation number of the corresponding object.
|
||||
// See metav1.ObjectMeta description for more information on Generation.
|
||||
|
@ -46,6 +46,8 @@ func (validatingWebhookConfigurationStrategy) NamespaceScoped() bool {
|
||||
func (validatingWebhookConfigurationStrategy) PrepareForCreate(ctx context.Context, obj runtime.Object) {
|
||||
ic := obj.(*admissionregistration.ValidatingWebhookConfiguration)
|
||||
ic.Generation = 1
|
||||
|
||||
admissionregistration.DropDisabledValidatingWebhookConfigurationFields(ic, nil)
|
||||
}
|
||||
|
||||
// PrepareForUpdate clears fields that are not allowed to be set by end users on update.
|
||||
@ -53,6 +55,7 @@ func (validatingWebhookConfigurationStrategy) PrepareForUpdate(ctx context.Conte
|
||||
newIC := obj.(*admissionregistration.ValidatingWebhookConfiguration)
|
||||
oldIC := old.(*admissionregistration.ValidatingWebhookConfiguration)
|
||||
|
||||
admissionregistration.DropDisabledValidatingWebhookConfigurationFields(newIC, oldIC)
|
||||
// Any changes to the spec increment the generation number, any changes to the
|
||||
// status should reflect the generation number of the corresponding object.
|
||||
// See metav1.ObjectMeta description for more information on Generation.
|
||||
|
@ -44,10 +44,38 @@ var _ = math.Inf
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
func (m *MatchCondition) Reset() { *m = MatchCondition{} }
|
||||
func (*MatchCondition) ProtoMessage() {}
|
||||
func (*MatchCondition) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{0}
|
||||
}
|
||||
func (m *MatchCondition) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *MatchCondition) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
func (m *MatchCondition) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_MatchCondition.Merge(m, src)
|
||||
}
|
||||
func (m *MatchCondition) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *MatchCondition) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_MatchCondition.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_MatchCondition proto.InternalMessageInfo
|
||||
|
||||
func (m *MutatingWebhook) Reset() { *m = MutatingWebhook{} }
|
||||
func (*MutatingWebhook) ProtoMessage() {}
|
||||
func (*MutatingWebhook) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{0}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{1}
|
||||
}
|
||||
func (m *MutatingWebhook) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -75,7 +103,7 @@ var xxx_messageInfo_MutatingWebhook proto.InternalMessageInfo
|
||||
func (m *MutatingWebhookConfiguration) Reset() { *m = MutatingWebhookConfiguration{} }
|
||||
func (*MutatingWebhookConfiguration) ProtoMessage() {}
|
||||
func (*MutatingWebhookConfiguration) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{1}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{2}
|
||||
}
|
||||
func (m *MutatingWebhookConfiguration) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -103,7 +131,7 @@ var xxx_messageInfo_MutatingWebhookConfiguration proto.InternalMessageInfo
|
||||
func (m *MutatingWebhookConfigurationList) Reset() { *m = MutatingWebhookConfigurationList{} }
|
||||
func (*MutatingWebhookConfigurationList) ProtoMessage() {}
|
||||
func (*MutatingWebhookConfigurationList) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{2}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{3}
|
||||
}
|
||||
func (m *MutatingWebhookConfigurationList) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -131,7 +159,7 @@ var xxx_messageInfo_MutatingWebhookConfigurationList proto.InternalMessageInfo
|
||||
func (m *Rule) Reset() { *m = Rule{} }
|
||||
func (*Rule) ProtoMessage() {}
|
||||
func (*Rule) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{3}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{4}
|
||||
}
|
||||
func (m *Rule) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -159,7 +187,7 @@ var xxx_messageInfo_Rule proto.InternalMessageInfo
|
||||
func (m *RuleWithOperations) Reset() { *m = RuleWithOperations{} }
|
||||
func (*RuleWithOperations) ProtoMessage() {}
|
||||
func (*RuleWithOperations) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{4}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{5}
|
||||
}
|
||||
func (m *RuleWithOperations) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -187,7 +215,7 @@ var xxx_messageInfo_RuleWithOperations proto.InternalMessageInfo
|
||||
func (m *ServiceReference) Reset() { *m = ServiceReference{} }
|
||||
func (*ServiceReference) ProtoMessage() {}
|
||||
func (*ServiceReference) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{5}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{6}
|
||||
}
|
||||
func (m *ServiceReference) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -215,7 +243,7 @@ var xxx_messageInfo_ServiceReference proto.InternalMessageInfo
|
||||
func (m *ValidatingWebhook) Reset() { *m = ValidatingWebhook{} }
|
||||
func (*ValidatingWebhook) ProtoMessage() {}
|
||||
func (*ValidatingWebhook) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{6}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{7}
|
||||
}
|
||||
func (m *ValidatingWebhook) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -243,7 +271,7 @@ var xxx_messageInfo_ValidatingWebhook proto.InternalMessageInfo
|
||||
func (m *ValidatingWebhookConfiguration) Reset() { *m = ValidatingWebhookConfiguration{} }
|
||||
func (*ValidatingWebhookConfiguration) ProtoMessage() {}
|
||||
func (*ValidatingWebhookConfiguration) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{7}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{8}
|
||||
}
|
||||
func (m *ValidatingWebhookConfiguration) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -271,7 +299,7 @@ var xxx_messageInfo_ValidatingWebhookConfiguration proto.InternalMessageInfo
|
||||
func (m *ValidatingWebhookConfigurationList) Reset() { *m = ValidatingWebhookConfigurationList{} }
|
||||
func (*ValidatingWebhookConfigurationList) ProtoMessage() {}
|
||||
func (*ValidatingWebhookConfigurationList) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{8}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{9}
|
||||
}
|
||||
func (m *ValidatingWebhookConfigurationList) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -299,7 +327,7 @@ var xxx_messageInfo_ValidatingWebhookConfigurationList proto.InternalMessageInfo
|
||||
func (m *WebhookClientConfig) Reset() { *m = WebhookClientConfig{} }
|
||||
func (*WebhookClientConfig) ProtoMessage() {}
|
||||
func (*WebhookClientConfig) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_aaac5994f79683e8, []int{9}
|
||||
return fileDescriptor_aaac5994f79683e8, []int{10}
|
||||
}
|
||||
func (m *WebhookClientConfig) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -325,6 +353,7 @@ func (m *WebhookClientConfig) XXX_DiscardUnknown() {
|
||||
var xxx_messageInfo_WebhookClientConfig proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*MatchCondition)(nil), "k8s.io.api.admissionregistration.v1.MatchCondition")
|
||||
proto.RegisterType((*MutatingWebhook)(nil), "k8s.io.api.admissionregistration.v1.MutatingWebhook")
|
||||
proto.RegisterType((*MutatingWebhookConfiguration)(nil), "k8s.io.api.admissionregistration.v1.MutatingWebhookConfiguration")
|
||||
proto.RegisterType((*MutatingWebhookConfigurationList)(nil), "k8s.io.api.admissionregistration.v1.MutatingWebhookConfigurationList")
|
||||
@ -342,79 +371,116 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_aaac5994f79683e8 = []byte{
|
||||
// 1105 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0x4f, 0x6f, 0x1b, 0x45,
|
||||
0x14, 0xcf, 0xc6, 0x76, 0x63, 0x8f, 0xf3, 0xa7, 0x19, 0xa0, 0x35, 0xa1, 0xf2, 0x5a, 0xae, 0x84,
|
||||
0x8c, 0x80, 0xdd, 0x26, 0x94, 0x52, 0x71, 0x41, 0xd9, 0xf0, 0x47, 0x11, 0x49, 0x1b, 0x4d, 0xda,
|
||||
0x14, 0xa1, 0x1c, 0x3a, 0x5e, 0x8f, 0xed, 0x21, 0xf6, 0xce, 0x6a, 0x66, 0xd6, 0x90, 0x1b, 0x1f,
|
||||
0x81, 0xaf, 0x00, 0x9f, 0x82, 0x1b, 0xe2, 0x96, 0x63, 0x8f, 0x39, 0xa0, 0x85, 0x2c, 0x17, 0x0e,
|
||||
0x7c, 0x82, 0x9c, 0xd0, 0xcc, 0xae, 0x77, 0xfd, 0x27, 0x09, 0x56, 0x0e, 0x3d, 0xe5, 0xe6, 0xf9,
|
||||
0xbd, 0x79, 0xbf, 0x37, 0xef, 0xed, 0x7b, 0xef, 0x27, 0x83, 0x9d, 0xa3, 0xc7, 0xc2, 0xa2, 0xcc,
|
||||
0x3e, 0x0a, 0x9a, 0x84, 0x7b, 0x44, 0x12, 0x61, 0x0f, 0x88, 0xd7, 0x62, 0xdc, 0x4e, 0x0c, 0xd8,
|
||||
0xa7, 0x36, 0x6e, 0xf5, 0xa9, 0x10, 0x94, 0x79, 0x9c, 0x74, 0xa8, 0x90, 0x1c, 0x4b, 0xca, 0x3c,
|
||||
0x7b, 0xb0, 0x6e, 0x77, 0x88, 0x47, 0x38, 0x96, 0xa4, 0x65, 0xf9, 0x9c, 0x49, 0x06, 0xef, 0xc7,
|
||||
0x4e, 0x16, 0xf6, 0xa9, 0x75, 0xa1, 0x93, 0x35, 0x58, 0x5f, 0xfb, 0xb0, 0x43, 0x65, 0x37, 0x68,
|
||||
0x5a, 0x2e, 0xeb, 0xdb, 0x1d, 0xd6, 0x61, 0xb6, 0xf6, 0x6d, 0x06, 0x6d, 0x7d, 0xd2, 0x07, 0xfd,
|
||||
0x2b, 0xe6, 0x5c, 0x7b, 0x98, 0x3d, 0xa4, 0x8f, 0xdd, 0x2e, 0xf5, 0x08, 0x3f, 0xb6, 0xfd, 0xa3,
|
||||
0x8e, 0x02, 0x84, 0xdd, 0x27, 0x12, 0x5f, 0xf0, 0x92, 0x35, 0xfb, 0x32, 0x2f, 0x1e, 0x78, 0x92,
|
||||
0xf6, 0xc9, 0x94, 0xc3, 0xa3, 0xff, 0x73, 0x10, 0x6e, 0x97, 0xf4, 0xf1, 0xa4, 0x5f, 0xfd, 0xb7,
|
||||
0x05, 0xb0, 0xb2, 0x1b, 0x48, 0x2c, 0xa9, 0xd7, 0x79, 0x41, 0x9a, 0x5d, 0xc6, 0x8e, 0x60, 0x0d,
|
||||
0xe4, 0x3d, 0xdc, 0x27, 0x15, 0xa3, 0x66, 0x34, 0x4a, 0xce, 0xe2, 0x49, 0x68, 0xce, 0x45, 0xa1,
|
||||
0x99, 0x7f, 0x82, 0xfb, 0x04, 0x69, 0x0b, 0xe4, 0x60, 0xd1, 0xed, 0x51, 0xe2, 0xc9, 0x2d, 0xe6,
|
||||
0xb5, 0x69, 0xa7, 0x32, 0x5f, 0x33, 0x1a, 0xe5, 0x8d, 0xc7, 0xd6, 0x0c, 0xf5, 0xb3, 0x92, 0x28,
|
||||
0x5b, 0x23, 0xfe, 0xce, 0x9b, 0x49, 0x8c, 0xc5, 0x51, 0x14, 0x8d, 0xc5, 0x80, 0x87, 0xa0, 0xc0,
|
||||
0x83, 0x1e, 0x11, 0x95, 0x5c, 0x2d, 0xd7, 0x28, 0x6f, 0x7c, 0x32, 0x53, 0x30, 0x14, 0xf4, 0xc8,
|
||||
0x0b, 0x2a, 0xbb, 0x4f, 0x7d, 0x12, 0x83, 0xc2, 0x59, 0x4a, 0x62, 0x15, 0x94, 0x4d, 0xa0, 0x98,
|
||||
0x14, 0xee, 0x80, 0xa5, 0x36, 0xa6, 0xbd, 0x80, 0x93, 0x3d, 0xd6, 0xa3, 0xee, 0x71, 0x25, 0xaf,
|
||||
0x93, 0x7f, 0x37, 0x0a, 0xcd, 0xa5, 0x2f, 0x47, 0x0d, 0xe7, 0xa1, 0xb9, 0x3a, 0x06, 0x3c, 0x3b,
|
||||
0xf6, 0x09, 0x1a, 0x77, 0x86, 0x9f, 0x83, 0x72, 0x1f, 0x4b, 0xb7, 0x9b, 0x70, 0x95, 0x34, 0x57,
|
||||
0x3d, 0x0a, 0xcd, 0xf2, 0x6e, 0x06, 0x9f, 0x87, 0xe6, 0xca, 0xc8, 0x51, 0xf3, 0x8c, 0xba, 0xc1,
|
||||
0x1f, 0xc0, 0xaa, 0xaa, 0xb6, 0xf0, 0xb1, 0x4b, 0xf6, 0x49, 0x8f, 0xb8, 0x92, 0xf1, 0x4a, 0x41,
|
||||
0x97, 0xfa, 0xa3, 0x91, 0xec, 0xd3, 0xef, 0x6d, 0xf9, 0x47, 0x1d, 0x05, 0x08, 0x4b, 0xb5, 0x95,
|
||||
0x4a, 0x7f, 0x07, 0x37, 0x49, 0x6f, 0xe8, 0xea, 0xbc, 0x15, 0x85, 0xe6, 0xea, 0x93, 0x49, 0x46,
|
||||
0x34, 0x1d, 0x04, 0x32, 0xb0, 0xcc, 0x9a, 0xdf, 0x11, 0x57, 0xa6, 0x61, 0xcb, 0xd7, 0x0f, 0x0b,
|
||||
0xa3, 0xd0, 0x5c, 0x7e, 0x3a, 0x46, 0x87, 0x26, 0xe8, 0x55, 0xc1, 0x04, 0x6d, 0x91, 0x2f, 0xda,
|
||||
0x6d, 0xe2, 0x4a, 0x51, 0xb9, 0x95, 0x15, 0x6c, 0x3f, 0x83, 0x55, 0xc1, 0xb2, 0xe3, 0x56, 0x0f,
|
||||
0x0b, 0x81, 0x46, 0xdd, 0xe0, 0xa7, 0x60, 0x59, 0xf5, 0x3a, 0x0b, 0xe4, 0x3e, 0x71, 0x99, 0xd7,
|
||||
0x12, 0x95, 0x85, 0x9a, 0xd1, 0x28, 0xc4, 0x2f, 0x78, 0x36, 0x66, 0x41, 0x13, 0x37, 0xe1, 0x73,
|
||||
0x70, 0x37, 0xed, 0x22, 0x44, 0x06, 0x94, 0x7c, 0x7f, 0x40, 0xb8, 0x3a, 0x88, 0x4a, 0xb1, 0x96,
|
||||
0x6b, 0x94, 0x9c, 0x77, 0xa2, 0xd0, 0xbc, 0xbb, 0x79, 0xf1, 0x15, 0x74, 0x99, 0x2f, 0x7c, 0x09,
|
||||
0x20, 0x27, 0xd4, 0x1b, 0x30, 0x57, 0xb7, 0x5f, 0xd2, 0x10, 0x40, 0xe7, 0xf7, 0x20, 0x0a, 0x4d,
|
||||
0x88, 0xa6, 0xac, 0xe7, 0xa1, 0x79, 0x67, 0x1a, 0xd5, 0xed, 0x71, 0x01, 0x57, 0xfd, 0xd4, 0x00,
|
||||
0xf7, 0x26, 0x26, 0x38, 0x9e, 0x98, 0x20, 0xee, 0x78, 0xf8, 0x12, 0x14, 0xd5, 0x87, 0x69, 0x61,
|
||||
0x89, 0xf5, 0x48, 0x97, 0x37, 0x1e, 0xcc, 0xf6, 0x19, 0xe3, 0x6f, 0xb6, 0x4b, 0x24, 0x76, 0x60,
|
||||
0x32, 0x34, 0x20, 0xc3, 0x50, 0xca, 0x0a, 0x0f, 0x40, 0x31, 0x89, 0x2c, 0x2a, 0xf3, 0x7a, 0x3a,
|
||||
0x1f, 0xce, 0x34, 0x9d, 0x13, 0xcf, 0x76, 0xf2, 0x2a, 0x0a, 0x4a, 0xb9, 0xea, 0xff, 0x18, 0xa0,
|
||||
0x76, 0x55, 0x6a, 0x3b, 0x54, 0x48, 0x78, 0x38, 0x95, 0x9e, 0x35, 0x63, 0x97, 0x52, 0x11, 0x27,
|
||||
0x77, 0x3b, 0x49, 0xae, 0x38, 0x44, 0x46, 0x52, 0x6b, 0x83, 0x02, 0x95, 0xa4, 0x3f, 0xcc, 0x6b,
|
||||
0xf3, 0x3a, 0x79, 0x8d, 0xbd, 0x39, 0xdb, 0x3f, 0xdb, 0x8a, 0x17, 0xc5, 0xf4, 0xf5, 0xdf, 0x0d,
|
||||
0x90, 0x57, 0x0b, 0x09, 0xbe, 0x0f, 0x4a, 0xd8, 0xa7, 0x5f, 0x71, 0x16, 0xf8, 0xa2, 0x62, 0xe8,
|
||||
0xce, 0x5b, 0x8a, 0x42, 0xb3, 0xb4, 0xb9, 0xb7, 0x1d, 0x83, 0x28, 0xb3, 0xc3, 0x75, 0x50, 0xc6,
|
||||
0x3e, 0x4d, 0x1b, 0x75, 0x5e, 0x5f, 0x5f, 0x51, 0x63, 0xb3, 0xb9, 0xb7, 0x9d, 0x36, 0xe7, 0xe8,
|
||||
0x1d, 0xc5, 0xcf, 0x89, 0x60, 0x01, 0x77, 0x93, 0x55, 0x9a, 0xf0, 0xa3, 0x21, 0x88, 0x32, 0x3b,
|
||||
0xfc, 0x00, 0x14, 0x84, 0xcb, 0x7c, 0x92, 0x6c, 0xc3, 0x3b, 0xea, 0xd9, 0xfb, 0x0a, 0x38, 0x0f,
|
||||
0xcd, 0x92, 0xfe, 0xa1, 0xdb, 0x32, 0xbe, 0x54, 0xff, 0xc5, 0x00, 0x70, 0x7a, 0xe1, 0xc2, 0xcf,
|
||||
0x00, 0x60, 0xe9, 0x29, 0x49, 0xc9, 0xd4, 0xbd, 0x94, 0xa2, 0xe7, 0xa1, 0xb9, 0x94, 0x9e, 0x34,
|
||||
0xe5, 0x88, 0x0b, 0xfc, 0x1a, 0xe4, 0xd5, 0x92, 0x4e, 0x54, 0xe6, 0xbd, 0x99, 0x17, 0x7f, 0x26,
|
||||
0x5d, 0xea, 0x84, 0x34, 0x49, 0xfd, 0x67, 0x03, 0xdc, 0xde, 0x27, 0x7c, 0x40, 0x5d, 0x82, 0x48,
|
||||
0x9b, 0x70, 0xe2, 0xb9, 0x04, 0xda, 0xa0, 0x94, 0x2e, 0xc1, 0x44, 0xf6, 0x56, 0x13, 0xdf, 0x52,
|
||||
0xba, 0x30, 0x51, 0x76, 0x27, 0x95, 0xc8, 0xf9, 0x4b, 0x25, 0xf2, 0x1e, 0xc8, 0xfb, 0x58, 0x76,
|
||||
0x2b, 0x39, 0x7d, 0xa3, 0xa8, 0xac, 0x7b, 0x58, 0x76, 0x91, 0x46, 0xb5, 0x95, 0x71, 0xa9, 0xeb,
|
||||
0x5a, 0x48, 0xac, 0x8c, 0x4b, 0xa4, 0xd1, 0xfa, 0x9f, 0xb7, 0xc0, 0xea, 0x01, 0xee, 0xd1, 0xd6,
|
||||
0x8d, 0x2c, 0xdf, 0xc8, 0xf2, 0x95, 0xb2, 0x0c, 0x6e, 0x64, 0xf9, 0x3a, 0xb2, 0x5c, 0xff, 0xc3,
|
||||
0x00, 0xd5, 0xa9, 0x09, 0x7b, 0xdd, 0xb2, 0xf9, 0xcd, 0x94, 0x6c, 0x3e, 0x9a, 0x69, 0x7a, 0xa6,
|
||||
0x1e, 0x3e, 0x25, 0x9c, 0xff, 0x1a, 0xa0, 0x7e, 0x75, 0x7a, 0xaf, 0x41, 0x3a, 0xbb, 0xe3, 0xd2,
|
||||
0xb9, 0x75, 0xbd, 0xdc, 0x66, 0x11, 0xcf, 0x5f, 0x0d, 0xf0, 0xc6, 0x05, 0xfb, 0x0b, 0xbe, 0x0d,
|
||||
0x72, 0x01, 0xef, 0x25, 0x2b, 0x78, 0x21, 0x0a, 0xcd, 0xdc, 0x73, 0xb4, 0x83, 0x14, 0x06, 0x0f,
|
||||
0xc1, 0x82, 0x88, 0x55, 0x20, 0xc9, 0xfc, 0xe3, 0x99, 0x9e, 0x37, 0xa9, 0x1c, 0x4e, 0x39, 0x0a,
|
||||
0xcd, 0x85, 0x21, 0x3a, 0xa4, 0x84, 0x0d, 0x50, 0x74, 0xb1, 0x13, 0x78, 0xad, 0x44, 0xb5, 0x16,
|
||||
0x9d, 0x45, 0x55, 0xa4, 0xad, 0xcd, 0x18, 0x43, 0xa9, 0xd5, 0xd9, 0x3e, 0x39, 0xab, 0xce, 0xbd,
|
||||
0x3a, 0xab, 0xce, 0x9d, 0x9e, 0x55, 0xe7, 0x7e, 0x8c, 0xaa, 0xc6, 0x49, 0x54, 0x35, 0x5e, 0x45,
|
||||
0x55, 0xe3, 0x34, 0xaa, 0x1a, 0x7f, 0x45, 0x55, 0xe3, 0xa7, 0xbf, 0xab, 0x73, 0xdf, 0xde, 0x9f,
|
||||
0xe1, 0xdf, 0xec, 0x7f, 0x01, 0x00, 0x00, 0xff, 0xff, 0x43, 0x44, 0x86, 0xf5, 0x0c, 0x0f, 0x00,
|
||||
// 1169 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x58, 0x4d, 0x6f, 0x1b, 0x45,
|
||||
0x18, 0xce, 0xc6, 0x36, 0xb1, 0xc7, 0x4e, 0xd2, 0x0c, 0xd0, 0x2e, 0xa5, 0xf2, 0x5a, 0xae, 0x84,
|
||||
0x82, 0x00, 0x6f, 0x9b, 0x96, 0x52, 0x71, 0x41, 0xb1, 0x29, 0x28, 0x22, 0x69, 0xa3, 0x49, 0x3f,
|
||||
0x10, 0xea, 0xa1, 0xe3, 0xf5, 0xd8, 0x1e, 0x62, 0xef, 0xac, 0x66, 0x66, 0x4d, 0x7b, 0xe3, 0x27,
|
||||
0xf0, 0x17, 0xe0, 0x4f, 0xc0, 0x95, 0x5b, 0x8f, 0xbd, 0x91, 0x03, 0x5a, 0x91, 0xe5, 0xc2, 0x81,
|
||||
0x5f, 0x90, 0x13, 0x9a, 0xd9, 0xf5, 0xae, 0xbf, 0x12, 0x56, 0x39, 0xe4, 0x94, 0x5b, 0xe6, 0x79,
|
||||
0xdf, 0xf7, 0x79, 0xe7, 0x19, 0xbf, 0x1f, 0xab, 0x80, 0xdd, 0xc3, 0xfb, 0xa2, 0x41, 0x99, 0x7d,
|
||||
0xe8, 0xb7, 0x09, 0x77, 0x89, 0x24, 0xc2, 0x1e, 0x11, 0xb7, 0xc3, 0xb8, 0x1d, 0x1b, 0xb0, 0x47,
|
||||
0x6d, 0xdc, 0x19, 0x52, 0x21, 0x28, 0x73, 0x39, 0xe9, 0x51, 0x21, 0x39, 0x96, 0x94, 0xb9, 0xf6,
|
||||
0xe8, 0xb6, 0xdd, 0x23, 0x2e, 0xe1, 0x58, 0x92, 0x4e, 0xc3, 0xe3, 0x4c, 0x32, 0x78, 0x33, 0x0a,
|
||||
0x6a, 0x60, 0x8f, 0x36, 0x16, 0x06, 0x35, 0x46, 0xb7, 0xaf, 0x7f, 0xd2, 0xa3, 0xb2, 0xef, 0xb7,
|
||||
0x1b, 0x0e, 0x1b, 0xda, 0x3d, 0xd6, 0x63, 0xb6, 0x8e, 0x6d, 0xfb, 0x5d, 0x7d, 0xd2, 0x07, 0xfd,
|
||||
0x57, 0xc4, 0x79, 0xfd, 0x6e, 0x7a, 0x91, 0x21, 0x76, 0xfa, 0xd4, 0x25, 0xfc, 0x95, 0xed, 0x1d,
|
||||
0xf6, 0x14, 0x20, 0xec, 0x21, 0x91, 0x78, 0xc1, 0x4d, 0xae, 0xdb, 0xa7, 0x45, 0x71, 0xdf, 0x95,
|
||||
0x74, 0x48, 0xe6, 0x02, 0xee, 0xfd, 0x5f, 0x80, 0x70, 0xfa, 0x64, 0x88, 0x67, 0xe3, 0xea, 0x5d,
|
||||
0xb0, 0xb6, 0x87, 0xa5, 0xd3, 0x6f, 0x31, 0xb7, 0x43, 0x95, 0x44, 0x58, 0x03, 0x79, 0x17, 0x0f,
|
||||
0x89, 0x69, 0xd4, 0x8c, 0xcd, 0x52, 0xb3, 0xf2, 0x3a, 0xb0, 0x96, 0xc2, 0xc0, 0xca, 0x3f, 0xc4,
|
||||
0x43, 0x82, 0xb4, 0x05, 0x6e, 0x01, 0x40, 0x5e, 0x7a, 0x9c, 0xe8, 0xe7, 0x31, 0x97, 0xb5, 0x1f,
|
||||
0x8c, 0xfd, 0xc0, 0x83, 0xc4, 0x82, 0x26, 0xbc, 0xea, 0xbf, 0x16, 0xc1, 0xfa, 0x9e, 0x2f, 0xb1,
|
||||
0xa4, 0x6e, 0xef, 0x19, 0x69, 0xf7, 0x19, 0x3b, 0xcc, 0x90, 0x89, 0x83, 0x8a, 0x33, 0xa0, 0xc4,
|
||||
0x95, 0x2d, 0xe6, 0x76, 0x69, 0x4f, 0xe7, 0x2a, 0x6f, 0xdd, 0x6f, 0x64, 0xf8, 0x9d, 0x1a, 0x71,
|
||||
0x96, 0xd6, 0x44, 0x7c, 0xf3, 0x9d, 0x38, 0x47, 0x65, 0x12, 0x45, 0x53, 0x39, 0xe0, 0x73, 0x50,
|
||||
0xe0, 0xfe, 0x80, 0x08, 0x33, 0x57, 0xcb, 0x6d, 0x96, 0xb7, 0x3e, 0xcb, 0x94, 0x0c, 0xf9, 0x03,
|
||||
0xf2, 0x8c, 0xca, 0xfe, 0x23, 0x8f, 0x44, 0xa0, 0x68, 0xae, 0xc6, 0xb9, 0x0a, 0xca, 0x26, 0x50,
|
||||
0x44, 0x0a, 0x77, 0xc1, 0x6a, 0x17, 0xd3, 0x81, 0xcf, 0xc9, 0x3e, 0x1b, 0x50, 0xe7, 0x95, 0x99,
|
||||
0xd7, 0xe2, 0x3f, 0x08, 0x03, 0x6b, 0xf5, 0xab, 0x49, 0xc3, 0x49, 0x60, 0x6d, 0x4c, 0x01, 0x8f,
|
||||
0x5f, 0x79, 0x04, 0x4d, 0x07, 0xc3, 0x2f, 0x41, 0x79, 0xa8, 0x7e, 0xbd, 0x98, 0xab, 0xa4, 0xb9,
|
||||
0xea, 0x61, 0x60, 0x95, 0xf7, 0x52, 0xf8, 0x24, 0xb0, 0xd6, 0x27, 0x8e, 0x9a, 0x67, 0x32, 0x0c,
|
||||
0xbe, 0x04, 0x1b, 0xea, 0xb5, 0x85, 0x87, 0x1d, 0x72, 0x40, 0x06, 0xc4, 0x91, 0x8c, 0x9b, 0x05,
|
||||
0xfd, 0xd4, 0x77, 0x26, 0xd4, 0x27, 0x75, 0xd5, 0xf0, 0x0e, 0x7b, 0x0a, 0x10, 0x0d, 0x55, 0xbe,
|
||||
0x4a, 0xfe, 0x2e, 0x6e, 0x93, 0xc1, 0x38, 0xb4, 0xf9, 0x6e, 0x18, 0x58, 0x1b, 0x0f, 0x67, 0x19,
|
||||
0xd1, 0x7c, 0x12, 0xc8, 0xc0, 0x1a, 0x6b, 0x7f, 0x4f, 0x1c, 0x99, 0xa4, 0x2d, 0x9f, 0x3f, 0x2d,
|
||||
0x0c, 0x03, 0x6b, 0xed, 0xd1, 0x14, 0x1d, 0x9a, 0xa1, 0x57, 0x0f, 0x26, 0x68, 0x87, 0x3c, 0xe8,
|
||||
0x76, 0x89, 0x23, 0x85, 0xf9, 0x56, 0xfa, 0x60, 0x07, 0x29, 0xac, 0x1e, 0x2c, 0x3d, 0xb6, 0x06,
|
||||
0x58, 0x08, 0x34, 0x19, 0x06, 0x3f, 0x07, 0x6b, 0xaa, 0xa7, 0x98, 0x2f, 0x0f, 0x88, 0xc3, 0xdc,
|
||||
0x8e, 0x30, 0x57, 0x6a, 0xc6, 0x66, 0x21, 0xba, 0xc1, 0xe3, 0x29, 0x0b, 0x9a, 0xf1, 0x84, 0x4f,
|
||||
0xc0, 0xb5, 0xa4, 0x8a, 0x10, 0x19, 0x51, 0xf2, 0xc3, 0x53, 0xc2, 0xd5, 0x41, 0x98, 0xc5, 0x5a,
|
||||
0x6e, 0xb3, 0xd4, 0x7c, 0x3f, 0x0c, 0xac, 0x6b, 0xdb, 0x8b, 0x5d, 0xd0, 0x69, 0xb1, 0xf0, 0x05,
|
||||
0x80, 0x9c, 0x50, 0x77, 0xc4, 0x1c, 0x5d, 0x7e, 0x71, 0x41, 0x00, 0xad, 0xef, 0x56, 0x18, 0x58,
|
||||
0x10, 0xcd, 0x59, 0x4f, 0x02, 0xeb, 0xea, 0x3c, 0xaa, 0xcb, 0x63, 0x01, 0x17, 0x1c, 0x81, 0xf5,
|
||||
0xe1, 0xd4, 0xa4, 0x10, 0x66, 0x45, 0x77, 0xc8, 0x9d, 0x4c, 0x1d, 0x32, 0x3d, 0x65, 0x9a, 0xd7,
|
||||
0xe2, 0xee, 0x58, 0x9f, 0xc6, 0x05, 0x9a, 0x4d, 0x52, 0x3f, 0x32, 0xc0, 0x8d, 0x99, 0xc9, 0x11,
|
||||
0x75, 0xaa, 0x1f, 0x91, 0xc3, 0x17, 0xa0, 0xa8, 0x0a, 0xa2, 0x83, 0x25, 0xd6, 0xa3, 0xa4, 0xbc,
|
||||
0x75, 0x2b, 0x5b, 0xf9, 0x44, 0xb5, 0xb2, 0x47, 0x24, 0x4e, 0xc7, 0x57, 0x8a, 0xa1, 0x84, 0x15,
|
||||
0x3e, 0x05, 0xc5, 0x38, 0xb3, 0x30, 0x97, 0xb5, 0xe6, 0xbb, 0xd9, 0x34, 0x4f, 0x5f, 0xbb, 0x99,
|
||||
0x57, 0x59, 0x50, 0xc2, 0x55, 0xff, 0xc7, 0x00, 0xb5, 0xb3, 0xa4, 0xed, 0x52, 0x21, 0xe1, 0xf3,
|
||||
0x39, 0x79, 0x8d, 0x8c, 0xdd, 0x41, 0x45, 0x24, 0xee, 0x4a, 0x2c, 0xae, 0x38, 0x46, 0x26, 0xa4,
|
||||
0x75, 0x41, 0x81, 0x4a, 0x32, 0x1c, 0xeb, 0xda, 0x3e, 0x8f, 0xae, 0xa9, 0x3b, 0xa7, 0x73, 0x6f,
|
||||
0x47, 0xf1, 0xa2, 0x88, 0xbe, 0xfe, 0xbb, 0x01, 0xf2, 0x6a, 0x10, 0xc2, 0x8f, 0x40, 0x09, 0x7b,
|
||||
0xf4, 0x6b, 0xce, 0x7c, 0x4f, 0x98, 0x86, 0xae, 0xf8, 0xd5, 0x30, 0xb0, 0x4a, 0xdb, 0xfb, 0x3b,
|
||||
0x11, 0x88, 0x52, 0x3b, 0xbc, 0x0d, 0xca, 0xd8, 0xa3, 0x49, 0x83, 0x2c, 0x6b, 0xf7, 0x75, 0xd5,
|
||||
0xae, 0xdb, 0xfb, 0x3b, 0x49, 0x53, 0x4c, 0xfa, 0x28, 0x7e, 0x4e, 0x04, 0xf3, 0xb9, 0x13, 0x8f,
|
||||
0xf0, 0x98, 0x1f, 0x8d, 0x41, 0x94, 0xda, 0xe1, 0xc7, 0xa0, 0x20, 0x1c, 0xe6, 0x91, 0x78, 0x0a,
|
||||
0x5f, 0x55, 0xd7, 0x3e, 0x50, 0xc0, 0x49, 0x60, 0x95, 0xf4, 0x1f, 0xba, 0x1d, 0x22, 0xa7, 0xfa,
|
||||
0x2f, 0x06, 0x80, 0xf3, 0x83, 0x1e, 0x7e, 0x01, 0x00, 0x4b, 0x4e, 0xb1, 0x24, 0x4b, 0xd7, 0x52,
|
||||
0x82, 0x9e, 0x04, 0xd6, 0x6a, 0x72, 0xd2, 0x94, 0x13, 0x21, 0xf0, 0x1b, 0x90, 0x57, 0xcb, 0x21,
|
||||
0xde, 0x6e, 0x1f, 0x66, 0x5e, 0x38, 0xe9, 0xca, 0x54, 0x27, 0xa4, 0x49, 0xea, 0x3f, 0x1b, 0xe0,
|
||||
0xca, 0x01, 0xe1, 0x23, 0xea, 0x10, 0x44, 0xba, 0x84, 0x13, 0xd7, 0x21, 0xd0, 0x06, 0xa5, 0x64,
|
||||
0xf8, 0xc6, 0xeb, 0x76, 0x23, 0x8e, 0x2d, 0x25, 0x83, 0x1a, 0xa5, 0x3e, 0xc9, 0x6a, 0x5e, 0x3e,
|
||||
0x75, 0x35, 0xdf, 0x00, 0x79, 0x0f, 0xcb, 0xbe, 0x99, 0xd3, 0x1e, 0x45, 0x65, 0xdd, 0xc7, 0xb2,
|
||||
0x8f, 0x34, 0xaa, 0xad, 0x8c, 0x4b, 0xfd, 0xae, 0x85, 0xd8, 0xca, 0xb8, 0x44, 0x1a, 0xad, 0xff,
|
||||
0xb1, 0x02, 0x36, 0x9e, 0xe2, 0x01, 0xed, 0x5c, 0x7e, 0x0e, 0x5c, 0x7e, 0x0e, 0x9c, 0xf9, 0x39,
|
||||
0x00, 0x2e, 0x3f, 0x07, 0xce, 0xf5, 0x39, 0xb0, 0x60, 0x59, 0x97, 0x2f, 0x62, 0x59, 0xff, 0x69,
|
||||
0x80, 0xea, 0x5c, 0x67, 0x5f, 0xf4, 0xba, 0xfe, 0x76, 0x6e, 0x5d, 0xdf, 0xcb, 0xa4, 0x7a, 0xee,
|
||||
0xe2, 0x73, 0x0b, 0xfb, 0x5f, 0x03, 0xd4, 0xcf, 0x96, 0x77, 0x01, 0x2b, 0xbb, 0x3f, 0xbd, 0xb2,
|
||||
0x5b, 0xe7, 0xd3, 0x96, 0x65, 0x69, 0xff, 0x66, 0x80, 0xb7, 0x17, 0xcc, 0x4d, 0xf8, 0x1e, 0xc8,
|
||||
0xf9, 0x7c, 0x10, 0x8f, 0xfe, 0x95, 0x30, 0xb0, 0x72, 0x4f, 0xd0, 0x2e, 0x52, 0x18, 0x7c, 0x0e,
|
||||
0x56, 0x44, 0xb4, 0x7d, 0x62, 0xe5, 0x9f, 0x66, 0xba, 0xde, 0xec, 0xc6, 0x6a, 0x96, 0xc3, 0xc0,
|
||||
0x5a, 0x19, 0xa3, 0x63, 0x4a, 0xb8, 0x09, 0x8a, 0x0e, 0x6e, 0xfa, 0x6e, 0x27, 0xde, 0x96, 0x95,
|
||||
0x66, 0x45, 0x3d, 0x52, 0x6b, 0x3b, 0xc2, 0x50, 0x62, 0x6d, 0xee, 0xbc, 0x3e, 0xae, 0x2e, 0xbd,
|
||||
0x39, 0xae, 0x2e, 0x1d, 0x1d, 0x57, 0x97, 0x7e, 0x0c, 0xab, 0xc6, 0xeb, 0xb0, 0x6a, 0xbc, 0x09,
|
||||
0xab, 0xc6, 0x51, 0x58, 0x35, 0xfe, 0x0a, 0xab, 0xc6, 0x4f, 0x7f, 0x57, 0x97, 0xbe, 0xbb, 0x99,
|
||||
0xe1, 0xbf, 0x04, 0xff, 0x05, 0x00, 0x00, 0xff, 0xff, 0x7f, 0xe1, 0x3a, 0x73, 0x64, 0x10, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
func (m *MatchCondition) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *MatchCondition) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *MatchCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
i -= len(m.Expression)
|
||||
copy(dAtA[i:], m.Expression)
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
i -= len(m.Name)
|
||||
copy(dAtA[i:], m.Name)
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *MutatingWebhook) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
@ -435,6 +501,20 @@ func (m *MutatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.MatchConditions) > 0 {
|
||||
for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x62
|
||||
}
|
||||
}
|
||||
if m.ObjectSelector != nil {
|
||||
{
|
||||
size, err := m.ObjectSelector.MarshalToSizedBuffer(dAtA[:i])
|
||||
@ -791,6 +871,20 @@ func (m *ValidatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.MatchConditions) > 0 {
|
||||
for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x5a
|
||||
}
|
||||
}
|
||||
if m.ObjectSelector != nil {
|
||||
{
|
||||
size, err := m.ObjectSelector.MarshalToSizedBuffer(dAtA[:i])
|
||||
@ -1036,6 +1130,19 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *MatchCondition) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Name)
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
l = len(m.Expression)
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *MutatingWebhook) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
@ -1085,6 +1192,12 @@ func (m *MutatingWebhook) Size() (n int) {
|
||||
l = m.ObjectSelector.Size()
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
if len(m.MatchConditions) > 0 {
|
||||
for _, e := range m.MatchConditions {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -1235,6 +1348,12 @@ func (m *ValidatingWebhook) Size() (n int) {
|
||||
l = m.ObjectSelector.Size()
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
if len(m.MatchConditions) > 0 {
|
||||
for _, e := range m.MatchConditions {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -1299,6 +1418,17 @@ func sovGenerated(x uint64) (n int) {
|
||||
func sozGenerated(x uint64) (n int) {
|
||||
return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (this *MatchCondition) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := strings.Join([]string{`&MatchCondition{`,
|
||||
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
|
||||
`Expression:` + fmt.Sprintf("%v", this.Expression) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func (this *MutatingWebhook) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
@ -1308,6 +1438,11 @@ func (this *MutatingWebhook) String() string {
|
||||
repeatedStringForRules += strings.Replace(strings.Replace(f.String(), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForRules += "}"
|
||||
repeatedStringForMatchConditions := "[]MatchCondition{"
|
||||
for _, f := range this.MatchConditions {
|
||||
repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForMatchConditions += "}"
|
||||
s := strings.Join([]string{`&MutatingWebhook{`,
|
||||
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
|
||||
`ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`,
|
||||
@ -1320,6 +1455,7 @@ func (this *MutatingWebhook) String() string {
|
||||
`MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`,
|
||||
`ReinvocationPolicy:` + valueToStringGenerated(this.ReinvocationPolicy) + `,`,
|
||||
`ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`,
|
||||
`MatchConditions:` + repeatedStringForMatchConditions + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
@ -1402,6 +1538,11 @@ func (this *ValidatingWebhook) String() string {
|
||||
repeatedStringForRules += strings.Replace(strings.Replace(f.String(), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForRules += "}"
|
||||
repeatedStringForMatchConditions := "[]MatchCondition{"
|
||||
for _, f := range this.MatchConditions {
|
||||
repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForMatchConditions += "}"
|
||||
s := strings.Join([]string{`&ValidatingWebhook{`,
|
||||
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
|
||||
`ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`,
|
||||
@ -1413,6 +1554,7 @@ func (this *ValidatingWebhook) String() string {
|
||||
`AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`,
|
||||
`MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`,
|
||||
`ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v1.LabelSelector", 1) + `,`,
|
||||
`MatchConditions:` + repeatedStringForMatchConditions + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
@ -1469,6 +1611,120 @@ func valueToStringGenerated(v interface{}) string {
|
||||
pv := reflect.Indirect(rv).Interface()
|
||||
return fmt.Sprintf("*%v", pv)
|
||||
}
|
||||
func (m *MatchCondition) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: MatchCondition: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: MatchCondition: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Name = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Expression", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Expression = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *MutatingWebhook) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
@ -1853,6 +2109,40 @@ func (m *MutatingWebhook) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 12:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MatchConditions", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.MatchConditions = append(m.MatchConditions, MatchCondition{})
|
||||
if err := m.MatchConditions[len(m.MatchConditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
@ -2920,6 +3210,40 @@ func (m *ValidatingWebhook) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 11:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MatchConditions", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.MatchConditions = append(m.MatchConditions, MatchCondition{})
|
||||
if err := m.MatchConditions[len(m.MatchConditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
|
@ -28,6 +28,35 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
|
||||
// Package-wide variables from generator "generated".
|
||||
option go_package = "k8s.io/api/admissionregistration/v1";
|
||||
|
||||
// MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.
|
||||
message MatchCondition {
|
||||
// Name is an identifier for this match condition, used for strategic merging of MatchConditions,
|
||||
// as well as providing an identifier for logging purposes. A good name should be descriptive of
|
||||
// the associated expression.
|
||||
// Name must be a qualified name consisting 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]') with an
|
||||
// optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')
|
||||
//
|
||||
// Required.
|
||||
optional string name = 1;
|
||||
|
||||
// Expression represents the expression which will be evaluated by CEL. Must evaluate to bool.
|
||||
// CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:
|
||||
//
|
||||
// 'object' - The object from the incoming request. The value is null for DELETE requests.
|
||||
// 'oldObject' - The existing object. The value is null for CREATE requests.
|
||||
// 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest).
|
||||
// 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
|
||||
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
|
||||
// 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
|
||||
// request resource.
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// Required.
|
||||
optional string expression = 2;
|
||||
}
|
||||
|
||||
// MutatingWebhook describes an admission webhook and the resources and operations it applies to.
|
||||
message MutatingWebhook {
|
||||
// The name of the admission webhook.
|
||||
@ -173,6 +202,28 @@ message MutatingWebhook {
|
||||
// Defaults to "Never".
|
||||
// +optional
|
||||
optional string reinvocationPolicy = 10;
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
repeated MatchCondition matchConditions = 12;
|
||||
}
|
||||
|
||||
// MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.
|
||||
@ -409,6 +460,28 @@ message ValidatingWebhook {
|
||||
// include any versions known to the API Server, calls to the webhook will fail
|
||||
// and be subject to the failure policy.
|
||||
repeated string admissionReviewVersions = 8;
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
repeated MatchCondition matchConditions = 11;
|
||||
}
|
||||
|
||||
// ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.
|
||||
|
@ -307,6 +307,28 @@ type ValidatingWebhook struct {
|
||||
// include any versions known to the API Server, calls to the webhook will fail
|
||||
// and be subject to the failure policy.
|
||||
AdmissionReviewVersions []string `json:"admissionReviewVersions" protobuf:"bytes,8,rep,name=admissionReviewVersions"`
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,11,opt,name=matchConditions"`
|
||||
}
|
||||
|
||||
// MutatingWebhook describes an admission webhook and the resources and operations it applies to.
|
||||
@ -454,6 +476,28 @@ type MutatingWebhook struct {
|
||||
// Defaults to "Never".
|
||||
// +optional
|
||||
ReinvocationPolicy *ReinvocationPolicyType `json:"reinvocationPolicy,omitempty" protobuf:"bytes,10,opt,name=reinvocationPolicy,casttype=ReinvocationPolicyType"`
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,12,opt,name=matchConditions"`
|
||||
}
|
||||
|
||||
// ReinvocationPolicyType specifies what type of policy the admission hook uses.
|
||||
@ -563,3 +607,32 @@ type ServiceReference struct {
|
||||
// +optional
|
||||
Port *int32 `json:"port,omitempty" protobuf:"varint,4,opt,name=port"`
|
||||
}
|
||||
|
||||
// MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.
|
||||
type MatchCondition struct {
|
||||
// Name is an identifier for this match condition, used for strategic merging of MatchConditions,
|
||||
// as well as providing an identifier for logging purposes. A good name should be descriptive of
|
||||
// the associated expression.
|
||||
// Name must be a qualified name consisting 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]') with an
|
||||
// optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')
|
||||
//
|
||||
// Required.
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
|
||||
// Expression represents the expression which will be evaluated by CEL. Must evaluate to bool.
|
||||
// CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:
|
||||
//
|
||||
// 'object' - The object from the incoming request. The value is null for DELETE requests.
|
||||
// 'oldObject' - The existing object. The value is null for CREATE requests.
|
||||
// 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest).
|
||||
// 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
|
||||
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
|
||||
// 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
|
||||
// request resource.
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// Required.
|
||||
Expression string `json:"expression" protobuf:"bytes,2,opt,name=expression"`
|
||||
}
|
||||
|
@ -27,6 +27,16 @@ package v1
|
||||
// Those methods can be generated by using hack/update-codegen.sh
|
||||
|
||||
// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT.
|
||||
var map_MatchCondition = map[string]string{
|
||||
"": "MatchCondition represents a condition which must by fulfilled for a request to be sent to a webhook.",
|
||||
"name": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting 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]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.",
|
||||
"expression": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.",
|
||||
}
|
||||
|
||||
func (MatchCondition) SwaggerDoc() map[string]string {
|
||||
return map_MatchCondition
|
||||
}
|
||||
|
||||
var map_MutatingWebhook = map[string]string{
|
||||
"": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.",
|
||||
"name": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.",
|
||||
@ -40,6 +50,7 @@ var map_MutatingWebhook = map[string]string{
|
||||
"timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.",
|
||||
"admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.",
|
||||
"reinvocationPolicy": "reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: the webhook will not be called more than once in a single admission evaluation.\n\nIfNeeded: the webhook will be called at least one additional time as part of the admission evaluation if the object being admitted is modified by other admission plugins after the initial webhook call. Webhooks that specify this option *must* be idempotent, able to process objects they previously admitted. Note: * the number of additional invocations is not guaranteed to be exactly one. * if additional invocations result in further modifications to the object, webhooks are not guaranteed to be invoked again. * webhooks that use this option may be reordered to minimize the number of additional invocations. * to validate an object after all mutations are guaranteed complete, use a validating admission webhook instead.\n\nDefaults to \"Never\".",
|
||||
"matchConditions": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
}
|
||||
|
||||
func (MutatingWebhook) SwaggerDoc() map[string]string {
|
||||
@ -111,6 +122,7 @@ var map_ValidatingWebhook = map[string]string{
|
||||
"sideEffects": "SideEffects states whether this webhook has side effects. Acceptable values are: None, NoneOnDryRun (webhooks created via v1beta1 may also specify Some or Unknown). Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some.",
|
||||
"timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 10 seconds.",
|
||||
"admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy.",
|
||||
"matchConditions": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
}
|
||||
|
||||
func (ValidatingWebhook) SwaggerDoc() map[string]string {
|
||||
|
@ -26,6 +26,22 @@ import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MatchCondition) DeepCopyInto(out *MatchCondition) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchCondition.
|
||||
func (in *MatchCondition) DeepCopy() *MatchCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MatchCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MutatingWebhook) DeepCopyInto(out *MutatingWebhook) {
|
||||
*out = *in
|
||||
@ -77,6 +93,11 @@ func (in *MutatingWebhook) DeepCopyInto(out *MutatingWebhook) {
|
||||
*out = new(ReinvocationPolicyType)
|
||||
**out = **in
|
||||
}
|
||||
if in.MatchConditions != nil {
|
||||
in, out := &in.MatchConditions, &out.MatchConditions
|
||||
*out = make([]MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -286,6 +307,11 @@ func (in *ValidatingWebhook) DeepCopyInto(out *ValidatingWebhook) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.MatchConditions != nil {
|
||||
in, out := &in.MatchConditions, &out.MatchConditions
|
||||
*out = make([]MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -45,10 +45,38 @@ var _ = math.Inf
|
||||
// proto package needs to be updated.
|
||||
const _ = proto.GoGoProtoPackageIsVersion3 // please upgrade the proto package
|
||||
|
||||
func (m *MatchCondition) Reset() { *m = MatchCondition{} }
|
||||
func (*MatchCondition) ProtoMessage() {}
|
||||
func (*MatchCondition) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{0}
|
||||
}
|
||||
func (m *MatchCondition) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
}
|
||||
func (m *MatchCondition) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
|
||||
b = b[:cap(b)]
|
||||
n, err := m.MarshalToSizedBuffer(b)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return b[:n], nil
|
||||
}
|
||||
func (m *MatchCondition) XXX_Merge(src proto.Message) {
|
||||
xxx_messageInfo_MatchCondition.Merge(m, src)
|
||||
}
|
||||
func (m *MatchCondition) XXX_Size() int {
|
||||
return m.Size()
|
||||
}
|
||||
func (m *MatchCondition) XXX_DiscardUnknown() {
|
||||
xxx_messageInfo_MatchCondition.DiscardUnknown(m)
|
||||
}
|
||||
|
||||
var xxx_messageInfo_MatchCondition proto.InternalMessageInfo
|
||||
|
||||
func (m *MutatingWebhook) Reset() { *m = MutatingWebhook{} }
|
||||
func (*MutatingWebhook) ProtoMessage() {}
|
||||
func (*MutatingWebhook) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{0}
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{1}
|
||||
}
|
||||
func (m *MutatingWebhook) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -76,7 +104,7 @@ var xxx_messageInfo_MutatingWebhook proto.InternalMessageInfo
|
||||
func (m *MutatingWebhookConfiguration) Reset() { *m = MutatingWebhookConfiguration{} }
|
||||
func (*MutatingWebhookConfiguration) ProtoMessage() {}
|
||||
func (*MutatingWebhookConfiguration) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{1}
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{2}
|
||||
}
|
||||
func (m *MutatingWebhookConfiguration) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -104,7 +132,7 @@ var xxx_messageInfo_MutatingWebhookConfiguration proto.InternalMessageInfo
|
||||
func (m *MutatingWebhookConfigurationList) Reset() { *m = MutatingWebhookConfigurationList{} }
|
||||
func (*MutatingWebhookConfigurationList) ProtoMessage() {}
|
||||
func (*MutatingWebhookConfigurationList) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{2}
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{3}
|
||||
}
|
||||
func (m *MutatingWebhookConfigurationList) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -132,7 +160,7 @@ var xxx_messageInfo_MutatingWebhookConfigurationList proto.InternalMessageInfo
|
||||
func (m *ServiceReference) Reset() { *m = ServiceReference{} }
|
||||
func (*ServiceReference) ProtoMessage() {}
|
||||
func (*ServiceReference) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{3}
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{4}
|
||||
}
|
||||
func (m *ServiceReference) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -160,7 +188,7 @@ var xxx_messageInfo_ServiceReference proto.InternalMessageInfo
|
||||
func (m *ValidatingWebhook) Reset() { *m = ValidatingWebhook{} }
|
||||
func (*ValidatingWebhook) ProtoMessage() {}
|
||||
func (*ValidatingWebhook) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{4}
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{5}
|
||||
}
|
||||
func (m *ValidatingWebhook) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -188,7 +216,7 @@ var xxx_messageInfo_ValidatingWebhook proto.InternalMessageInfo
|
||||
func (m *ValidatingWebhookConfiguration) Reset() { *m = ValidatingWebhookConfiguration{} }
|
||||
func (*ValidatingWebhookConfiguration) ProtoMessage() {}
|
||||
func (*ValidatingWebhookConfiguration) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{5}
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{6}
|
||||
}
|
||||
func (m *ValidatingWebhookConfiguration) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -216,7 +244,7 @@ var xxx_messageInfo_ValidatingWebhookConfiguration proto.InternalMessageInfo
|
||||
func (m *ValidatingWebhookConfigurationList) Reset() { *m = ValidatingWebhookConfigurationList{} }
|
||||
func (*ValidatingWebhookConfigurationList) ProtoMessage() {}
|
||||
func (*ValidatingWebhookConfigurationList) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{6}
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{7}
|
||||
}
|
||||
func (m *ValidatingWebhookConfigurationList) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -244,7 +272,7 @@ var xxx_messageInfo_ValidatingWebhookConfigurationList proto.InternalMessageInfo
|
||||
func (m *WebhookClientConfig) Reset() { *m = WebhookClientConfig{} }
|
||||
func (*WebhookClientConfig) ProtoMessage() {}
|
||||
func (*WebhookClientConfig) Descriptor() ([]byte, []int) {
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{7}
|
||||
return fileDescriptor_abeea74cbc46f55a, []int{8}
|
||||
}
|
||||
func (m *WebhookClientConfig) XXX_Unmarshal(b []byte) error {
|
||||
return m.Unmarshal(b)
|
||||
@ -270,6 +298,7 @@ func (m *WebhookClientConfig) XXX_DiscardUnknown() {
|
||||
var xxx_messageInfo_WebhookClientConfig proto.InternalMessageInfo
|
||||
|
||||
func init() {
|
||||
proto.RegisterType((*MatchCondition)(nil), "k8s.io.api.admissionregistration.v1beta1.MatchCondition")
|
||||
proto.RegisterType((*MutatingWebhook)(nil), "k8s.io.api.admissionregistration.v1beta1.MutatingWebhook")
|
||||
proto.RegisterType((*MutatingWebhookConfiguration)(nil), "k8s.io.api.admissionregistration.v1beta1.MutatingWebhookConfiguration")
|
||||
proto.RegisterType((*MutatingWebhookConfigurationList)(nil), "k8s.io.api.admissionregistration.v1beta1.MutatingWebhookConfigurationList")
|
||||
@ -285,68 +314,106 @@ func init() {
|
||||
}
|
||||
|
||||
var fileDescriptor_abeea74cbc46f55a = []byte{
|
||||
// 974 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0x49, 0x6f, 0xdb, 0x46,
|
||||
0x14, 0x36, 0x2d, 0x29, 0x92, 0x46, 0xb2, 0x13, 0x4d, 0x97, 0xb0, 0x6e, 0x40, 0x0a, 0x3a, 0x14,
|
||||
0xba, 0x94, 0x4c, 0x9c, 0xa2, 0x4b, 0x8a, 0x1e, 0x42, 0xb7, 0x41, 0x0b, 0xd8, 0x4e, 0x3a, 0xce,
|
||||
0x02, 0xb4, 0x29, 0x90, 0x11, 0xf5, 0x24, 0x4d, 0x45, 0x72, 0x04, 0xce, 0x50, 0xa9, 0x6f, 0xfd,
|
||||
0x09, 0xfd, 0x0b, 0xfd, 0x21, 0xbd, 0xf5, 0xe0, 0x63, 0x8e, 0xb9, 0x94, 0xa8, 0xd9, 0x5e, 0x7b,
|
||||
0xe8, 0xd5, 0xa7, 0x82, 0x8b, 0x76, 0x39, 0x21, 0x5c, 0x20, 0x27, 0xdf, 0x34, 0xdf, 0xe3, 0xf7,
|
||||
0xbd, 0x79, 0x6f, 0xde, 0x02, 0xa1, 0x6f, 0x87, 0x9f, 0x0a, 0x83, 0x71, 0x73, 0x18, 0x74, 0xc0,
|
||||
0xf7, 0x40, 0x82, 0x30, 0xc7, 0xe0, 0x75, 0xb9, 0x6f, 0x66, 0x06, 0x3a, 0x62, 0x26, 0xed, 0xba,
|
||||
0x4c, 0x08, 0xc6, 0x3d, 0x1f, 0xfa, 0x4c, 0x48, 0x9f, 0x4a, 0xc6, 0x3d, 0x73, 0x7c, 0xab, 0x03,
|
||||
0x92, 0xde, 0x32, 0xfb, 0xe0, 0x81, 0x4f, 0x25, 0x74, 0x8d, 0x91, 0xcf, 0x25, 0xc7, 0xed, 0x94,
|
||||
0x69, 0xd0, 0x11, 0x33, 0xd6, 0x32, 0x8d, 0x8c, 0xb9, 0xf3, 0x61, 0x9f, 0xc9, 0x41, 0xd0, 0x31,
|
||||
0x6c, 0xee, 0x9a, 0x7d, 0xde, 0xe7, 0x66, 0x22, 0xd0, 0x09, 0x7a, 0xc9, 0x29, 0x39, 0x24, 0xbf,
|
||||
0x52, 0xe1, 0x9d, 0xdb, 0x39, 0xae, 0xb4, 0x7c, 0x9b, 0x9d, 0x8f, 0x66, 0x24, 0x97, 0xda, 0x03,
|
||||
0xe6, 0x81, 0x7f, 0x6c, 0x8e, 0x86, 0xfd, 0x18, 0x10, 0xa6, 0x0b, 0x92, 0xae, 0x63, 0x99, 0xe7,
|
||||
0xb1, 0xfc, 0xc0, 0x93, 0xcc, 0x85, 0x15, 0xc2, 0xc7, 0xaf, 0x23, 0x08, 0x7b, 0x00, 0x2e, 0x5d,
|
||||
0xe6, 0xb5, 0x7e, 0x2f, 0xa3, 0xab, 0x07, 0x81, 0xa4, 0x92, 0x79, 0xfd, 0x27, 0xd0, 0x19, 0x70,
|
||||
0x3e, 0xc4, 0x4d, 0x54, 0xf4, 0xa8, 0x0b, 0xaa, 0xd2, 0x54, 0xda, 0x55, 0xab, 0x7e, 0x12, 0xea,
|
||||
0x1b, 0x51, 0xa8, 0x17, 0x0f, 0xa9, 0x0b, 0x24, 0xb1, 0xe0, 0xe7, 0xa8, 0x6e, 0x3b, 0x0c, 0x3c,
|
||||
0xb9, 0xc7, 0xbd, 0x1e, 0xeb, 0xab, 0x9b, 0x4d, 0xa5, 0x5d, 0xdb, 0xfd, 0xc2, 0xc8, 0x9b, 0x79,
|
||||
0x23, 0x73, 0xb5, 0x37, 0x27, 0x62, 0xbd, 0x9d, 0x39, 0xaa, 0xcf, 0xa3, 0x64, 0xc1, 0x11, 0x7e,
|
||||
0x8a, 0x4a, 0x7e, 0xe0, 0x80, 0x50, 0x0b, 0xcd, 0x42, 0xbb, 0xb6, 0xfb, 0x49, 0x1e, 0x8f, 0x06,
|
||||
0x09, 0x1c, 0x78, 0xc2, 0xe4, 0xe0, 0xfe, 0x08, 0x52, 0x50, 0x58, 0x5b, 0x99, 0xaf, 0x52, 0x6c,
|
||||
0x13, 0x24, 0x15, 0xc5, 0xfb, 0x68, 0xab, 0x47, 0x99, 0x13, 0xf8, 0xf0, 0x80, 0x3b, 0xcc, 0x3e,
|
||||
0x56, 0x8b, 0x49, 0x06, 0x3e, 0x88, 0x42, 0x7d, 0xeb, 0xde, 0xbc, 0xe1, 0x2c, 0xd4, 0x1b, 0x0b,
|
||||
0xc0, 0xc3, 0xe3, 0x11, 0x90, 0x45, 0x32, 0xfe, 0x12, 0xd5, 0x5c, 0x2a, 0xed, 0x41, 0xa6, 0x55,
|
||||
0x4d, 0xb4, 0x5a, 0x51, 0xa8, 0xd7, 0x0e, 0x66, 0xf0, 0x59, 0xa8, 0x5f, 0x9d, 0x3b, 0x26, 0x3a,
|
||||
0xf3, 0x34, 0xfc, 0x13, 0x6a, 0xc4, 0x29, 0x17, 0x23, 0x6a, 0xc3, 0x11, 0x38, 0x60, 0x4b, 0xee,
|
||||
0xab, 0xa5, 0x24, 0xdf, 0xb7, 0xe7, 0xa2, 0x9f, 0x3e, 0xba, 0x31, 0x1a, 0xf6, 0x63, 0x40, 0x18,
|
||||
0x71, 0x6d, 0xc5, 0xe1, 0xef, 0xd3, 0x0e, 0x38, 0x13, 0xaa, 0xf5, 0x4e, 0x14, 0xea, 0x8d, 0xc3,
|
||||
0x65, 0x45, 0xb2, 0xea, 0x04, 0x73, 0xb4, 0xcd, 0x3b, 0x3f, 0x82, 0x2d, 0xa7, 0x6e, 0x6b, 0x17,
|
||||
0x77, 0x8b, 0xa3, 0x50, 0xdf, 0xbe, 0xbf, 0x20, 0x47, 0x96, 0xe4, 0xe3, 0x84, 0x09, 0xd6, 0x85,
|
||||
0xaf, 0x7a, 0x3d, 0xb0, 0xa5, 0x50, 0xaf, 0xcc, 0x12, 0x76, 0x34, 0x83, 0xe3, 0x84, 0xcd, 0x8e,
|
||||
0x7b, 0x0e, 0x15, 0x82, 0xcc, 0xd3, 0xf0, 0x1d, 0xb4, 0x1d, 0x17, 0x3c, 0x0f, 0xe4, 0x11, 0xd8,
|
||||
0xdc, 0xeb, 0x0a, 0xb5, 0xdc, 0x54, 0xda, 0xa5, 0xf4, 0x06, 0x0f, 0x17, 0x2c, 0x64, 0xe9, 0x4b,
|
||||
0xfc, 0x08, 0x5d, 0x9f, 0x56, 0x11, 0x81, 0x31, 0x83, 0xe7, 0x8f, 0xc1, 0x8f, 0x0f, 0x42, 0xad,
|
||||
0x34, 0x0b, 0xed, 0xaa, 0xf5, 0x7e, 0x14, 0xea, 0xd7, 0xef, 0xae, 0xff, 0x84, 0x9c, 0xc7, 0xc5,
|
||||
0xcf, 0x10, 0xf6, 0x81, 0x79, 0x63, 0x6e, 0x27, 0xe5, 0x97, 0x15, 0x04, 0x4a, 0xe2, 0xbb, 0x19,
|
||||
0x85, 0x3a, 0x26, 0x2b, 0xd6, 0xb3, 0x50, 0x7f, 0x77, 0x15, 0x4d, 0xca, 0x63, 0x8d, 0x56, 0xeb,
|
||||
0x0f, 0x05, 0xdd, 0x58, 0x6a, 0xe3, 0xb4, 0x63, 0x82, 0xb4, 0xe2, 0xf1, 0x33, 0x54, 0x89, 0x1f,
|
||||
0xa6, 0x4b, 0x25, 0x4d, 0xfa, 0xba, 0xb6, 0x7b, 0x33, 0xdf, 0x33, 0xa6, 0x6f, 0x76, 0x00, 0x92,
|
||||
0x5a, 0x38, 0x6b, 0x1a, 0x34, 0xc3, 0xc8, 0x54, 0x15, 0x7f, 0x8f, 0x2a, 0x99, 0x67, 0xa1, 0x6e,
|
||||
0x26, 0xdd, 0xf9, 0x59, 0xfe, 0x79, 0xb0, 0x74, 0x77, 0xab, 0x18, 0xbb, 0x22, 0x53, 0xc1, 0xd6,
|
||||
0x3f, 0x0a, 0x6a, 0xbe, 0x2a, 0xbe, 0x7d, 0x26, 0x24, 0x7e, 0xba, 0x12, 0xa3, 0x91, 0xb3, 0x54,
|
||||
0x99, 0x48, 0x23, 0xbc, 0x96, 0x45, 0x58, 0x99, 0x20, 0x73, 0xf1, 0x0d, 0x51, 0x89, 0x49, 0x70,
|
||||
0x27, 0xc1, 0xdd, 0xbb, 0x70, 0x70, 0x0b, 0x17, 0x9f, 0x4d, 0xa2, 0x6f, 0x62, 0x71, 0x92, 0xfa,
|
||||
0x68, 0xfd, 0xaa, 0xa0, 0x6b, 0x47, 0xe0, 0x8f, 0x99, 0x0d, 0x04, 0x7a, 0xe0, 0x83, 0x67, 0x03,
|
||||
0x36, 0x51, 0x75, 0xda, 0xa5, 0xd9, 0x70, 0x6e, 0x64, 0xec, 0xea, 0xb4, 0xa3, 0xc9, 0xec, 0x9b,
|
||||
0xe9, 0x20, 0xdf, 0x3c, 0x77, 0x90, 0xdf, 0x40, 0xc5, 0x11, 0x95, 0x03, 0xb5, 0x90, 0x7c, 0x51,
|
||||
0x89, 0xad, 0x0f, 0xa8, 0x1c, 0x90, 0x04, 0x4d, 0xac, 0xdc, 0x97, 0xc9, 0x18, 0x2c, 0x65, 0x56,
|
||||
0xee, 0x4b, 0x92, 0xa0, 0xad, 0xbf, 0xaf, 0xa0, 0xc6, 0x63, 0xea, 0xb0, 0xee, 0xe5, 0xf2, 0xb8,
|
||||
0x5c, 0x1e, 0xaf, 0x5f, 0x1e, 0xe8, 0x72, 0x79, 0x5c, 0x64, 0x79, 0xb4, 0x4e, 0x15, 0xa4, 0xad,
|
||||
0xb4, 0xd9, 0x9b, 0x1e, 0xee, 0x3f, 0xac, 0x0c, 0xf7, 0xcf, 0xf3, 0xf7, 0xeb, 0xca, 0xed, 0x57,
|
||||
0xc6, 0xfb, 0xbf, 0x0a, 0x6a, 0xbd, 0x3a, 0xc6, 0x37, 0x30, 0xe0, 0xdd, 0xc5, 0x01, 0xff, 0xf5,
|
||||
0xff, 0x08, 0x30, 0xcf, 0x88, 0xff, 0x4d, 0x41, 0x6f, 0xad, 0x99, 0x64, 0xf8, 0x3d, 0x54, 0x08,
|
||||
0x7c, 0x27, 0x9b, 0xc8, 0xe5, 0x28, 0xd4, 0x0b, 0x8f, 0xc8, 0x3e, 0x89, 0x31, 0x4c, 0x51, 0x59,
|
||||
0xa4, 0x4b, 0x21, 0x0b, 0xff, 0x4e, 0xfe, 0x3b, 0x2e, 0x6f, 0x13, 0xab, 0x16, 0x85, 0x7a, 0x79,
|
||||
0x82, 0x4e, 0x74, 0x71, 0x1b, 0x55, 0x6c, 0x6a, 0x05, 0x5e, 0xd7, 0x49, 0xd7, 0x46, 0xdd, 0xaa,
|
||||
0xc7, 0xe9, 0xda, 0xbb, 0x9b, 0x62, 0x64, 0x6a, 0xb5, 0x0e, 0x4f, 0x4e, 0xb5, 0x8d, 0x17, 0xa7,
|
||||
0xda, 0xc6, 0xcb, 0x53, 0x6d, 0xe3, 0xe7, 0x48, 0x53, 0x4e, 0x22, 0x4d, 0x79, 0x11, 0x69, 0xca,
|
||||
0xcb, 0x48, 0x53, 0xfe, 0x8c, 0x34, 0xe5, 0x97, 0xbf, 0xb4, 0x8d, 0xef, 0xda, 0x79, 0xff, 0xc6,
|
||||
0xfd, 0x17, 0x00, 0x00, 0xff, 0xff, 0x7e, 0xc9, 0x34, 0x4c, 0x0a, 0x0e, 0x00, 0x00,
|
||||
// 1041 bytes of a gzipped FileDescriptorProto
|
||||
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x57, 0x4f, 0x73, 0xdb, 0xc4,
|
||||
0x1b, 0x8e, 0xe2, 0xf8, 0x17, 0x67, 0xed, 0x24, 0xcd, 0xfe, 0x80, 0x88, 0xd0, 0xb1, 0x3c, 0x3e,
|
||||
0x30, 0xbe, 0x20, 0xb5, 0x29, 0x03, 0xa5, 0x0c, 0x87, 0x2a, 0xb4, 0x03, 0x33, 0x49, 0x5a, 0x36,
|
||||
0xfd, 0x33, 0x03, 0x65, 0xa6, 0x6b, 0xf9, 0xb5, 0xbd, 0x58, 0xd2, 0x7a, 0xb4, 0xab, 0xb4, 0x19,
|
||||
0x2e, 0x7c, 0x04, 0xbe, 0x02, 0x1f, 0x84, 0x03, 0xb7, 0x1c, 0x7b, 0xec, 0x05, 0x0d, 0x11, 0x67,
|
||||
0x0e, 0x5c, 0x73, 0x62, 0xb4, 0x52, 0x6c, 0xcb, 0x76, 0x5a, 0x11, 0x66, 0x72, 0xca, 0xcd, 0xfb,
|
||||
0xbc, 0xfb, 0xbe, 0xcf, 0x3e, 0xab, 0x77, 0xdf, 0x67, 0x8c, 0xbe, 0x19, 0xdc, 0x16, 0x26, 0xe3,
|
||||
0xd6, 0x20, 0x6c, 0x43, 0xe0, 0x83, 0x04, 0x61, 0x1d, 0x82, 0xdf, 0xe1, 0x81, 0x95, 0x05, 0xe8,
|
||||
0x90, 0x59, 0xb4, 0xe3, 0x31, 0x21, 0x18, 0xf7, 0x03, 0xe8, 0x31, 0x21, 0x03, 0x2a, 0x19, 0xf7,
|
||||
0xad, 0xc3, 0x9b, 0x6d, 0x90, 0xf4, 0xa6, 0xd5, 0x03, 0x1f, 0x02, 0x2a, 0xa1, 0x63, 0x0e, 0x03,
|
||||
0x2e, 0x39, 0x6e, 0xa5, 0x99, 0x26, 0x1d, 0x32, 0x73, 0x6e, 0xa6, 0x99, 0x65, 0x6e, 0x7d, 0xd4,
|
||||
0x63, 0xb2, 0x1f, 0xb6, 0x4d, 0x87, 0x7b, 0x56, 0x8f, 0xf7, 0xb8, 0xa5, 0x0a, 0xb4, 0xc3, 0xae,
|
||||
0x5a, 0xa9, 0x85, 0xfa, 0x95, 0x16, 0xde, 0xba, 0x55, 0xe0, 0x48, 0xd3, 0xa7, 0xd9, 0xfa, 0x78,
|
||||
0x9c, 0xe4, 0x51, 0xa7, 0xcf, 0x7c, 0x08, 0x8e, 0xac, 0xe1, 0xa0, 0x97, 0x00, 0xc2, 0xf2, 0x40,
|
||||
0xd2, 0x79, 0x59, 0xd6, 0x79, 0x59, 0x41, 0xe8, 0x4b, 0xe6, 0xc1, 0x4c, 0xc2, 0x27, 0x6f, 0x4b,
|
||||
0x10, 0x4e, 0x1f, 0x3c, 0x3a, 0x9d, 0xd7, 0xec, 0xa2, 0xb5, 0x3d, 0x2a, 0x9d, 0xfe, 0x0e, 0xf7,
|
||||
0x3b, 0x2c, 0xd1, 0x80, 0x1b, 0x68, 0xc9, 0xa7, 0x1e, 0xe8, 0x5a, 0x43, 0x6b, 0xad, 0xd8, 0xb5,
|
||||
0xe3, 0xc8, 0x58, 0x88, 0x23, 0x63, 0x69, 0x9f, 0x7a, 0x40, 0x54, 0x04, 0x6f, 0x23, 0x04, 0x2f,
|
||||
0x87, 0x01, 0x28, 0xfd, 0xfa, 0xa2, 0xda, 0x87, 0xb3, 0x7d, 0xe8, 0xde, 0x28, 0x42, 0x26, 0x76,
|
||||
0x35, 0x7f, 0xab, 0xa0, 0xf5, 0xbd, 0x50, 0x52, 0xc9, 0xfc, 0xde, 0x53, 0x68, 0xf7, 0x39, 0x1f,
|
||||
0x14, 0x60, 0x7a, 0x81, 0x6a, 0x8e, 0xcb, 0xc0, 0x97, 0x3b, 0xdc, 0xef, 0xb2, 0x9e, 0xe2, 0xaa,
|
||||
0x6e, 0x7f, 0x61, 0x16, 0xfd, 0xc2, 0x66, 0x46, 0xb5, 0x33, 0x51, 0xc4, 0x7e, 0x27, 0x23, 0xaa,
|
||||
0x4d, 0xa2, 0x24, 0x47, 0x84, 0x9f, 0xa1, 0x72, 0x10, 0xba, 0x20, 0xf4, 0x52, 0xa3, 0xd4, 0xaa,
|
||||
0x6e, 0x7f, 0x5a, 0x84, 0xd1, 0x24, 0xa1, 0x0b, 0x4f, 0x99, 0xec, 0x3f, 0x18, 0x42, 0x0a, 0x0a,
|
||||
0x7b, 0x35, 0xe3, 0x2a, 0x27, 0x31, 0x41, 0xd2, 0xa2, 0x78, 0x17, 0xad, 0x76, 0x29, 0x73, 0xc3,
|
||||
0x00, 0x1e, 0x72, 0x97, 0x39, 0x47, 0xfa, 0x92, 0xba, 0x81, 0x0f, 0xe3, 0xc8, 0x58, 0xbd, 0x3f,
|
||||
0x19, 0x38, 0x8d, 0x8c, 0x8d, 0x1c, 0xf0, 0xe8, 0x68, 0x08, 0x24, 0x9f, 0x8c, 0xbf, 0x44, 0x55,
|
||||
0x2f, 0xf9, 0x84, 0x59, 0xad, 0x15, 0x55, 0xab, 0x19, 0x47, 0x46, 0x75, 0x6f, 0x0c, 0x9f, 0x46,
|
||||
0xc6, 0xfa, 0xc4, 0x52, 0xd5, 0x99, 0x4c, 0xc3, 0x2f, 0xd1, 0x46, 0x72, 0xe5, 0x62, 0x48, 0x1d,
|
||||
0x38, 0x00, 0x17, 0x1c, 0xc9, 0x03, 0xbd, 0xac, 0xee, 0xfb, 0xd6, 0x84, 0xfa, 0x51, 0x73, 0x99,
|
||||
0xc3, 0x41, 0x2f, 0x01, 0x84, 0x99, 0xf4, 0x70, 0x22, 0x7f, 0x97, 0xb6, 0xc1, 0x3d, 0x4b, 0xb5,
|
||||
0xdf, 0x8d, 0x23, 0x63, 0x63, 0x7f, 0xba, 0x22, 0x99, 0x25, 0xc1, 0x1c, 0xad, 0xf1, 0xf6, 0x0f,
|
||||
0xe0, 0xc8, 0x11, 0x6d, 0xf5, 0xe2, 0xb4, 0x38, 0x8e, 0x8c, 0xb5, 0x07, 0xb9, 0x72, 0x64, 0xaa,
|
||||
0x7c, 0x72, 0x61, 0x82, 0x75, 0xe0, 0x5e, 0xb7, 0x0b, 0x8e, 0x14, 0xfa, 0xff, 0xc6, 0x17, 0x76,
|
||||
0x30, 0x86, 0x93, 0x0b, 0x1b, 0x2f, 0x77, 0x5c, 0x2a, 0x04, 0x99, 0x4c, 0xc3, 0x77, 0xd0, 0x5a,
|
||||
0xf2, 0xb0, 0x78, 0x28, 0x0f, 0xc0, 0xe1, 0x7e, 0x47, 0xe8, 0xcb, 0x0d, 0xad, 0x55, 0x4e, 0x4f,
|
||||
0xf0, 0x28, 0x17, 0x21, 0x53, 0x3b, 0xf1, 0x63, 0xb4, 0x39, 0xea, 0x22, 0x02, 0x87, 0x0c, 0x5e,
|
||||
0x3c, 0x81, 0x20, 0x59, 0x08, 0xbd, 0xd2, 0x28, 0xb5, 0x56, 0xec, 0x0f, 0xe2, 0xc8, 0xd8, 0xbc,
|
||||
0x3b, 0x7f, 0x0b, 0x39, 0x2f, 0x17, 0x3f, 0x47, 0x38, 0x00, 0xe6, 0x1f, 0x72, 0x47, 0xb5, 0x5f,
|
||||
0xd6, 0x10, 0x48, 0xe9, 0xbb, 0x11, 0x47, 0x06, 0x26, 0x33, 0xd1, 0xd3, 0xc8, 0x78, 0x6f, 0x16,
|
||||
0x55, 0xed, 0x31, 0xa7, 0x16, 0xfe, 0x11, 0xad, 0x7b, 0xb9, 0x71, 0x21, 0xf4, 0x9a, 0x7a, 0x21,
|
||||
0xb7, 0x8b, 0xbf, 0xc9, 0xfc, 0xbc, 0xb1, 0x37, 0xb3, 0x27, 0xb2, 0x9e, 0xc7, 0x05, 0x99, 0x66,
|
||||
0x6a, 0xfe, 0xae, 0xa1, 0xeb, 0x53, 0x33, 0x24, 0x7d, 0xae, 0x61, 0xca, 0x80, 0x9f, 0xa3, 0x4a,
|
||||
0xd2, 0x15, 0x1d, 0x2a, 0xa9, 0x1a, 0x2a, 0xd5, 0xed, 0x1b, 0xc5, 0x7a, 0x28, 0x6d, 0x98, 0x3d,
|
||||
0x90, 0x74, 0x3c, 0xc8, 0xc6, 0x18, 0x19, 0x55, 0xc5, 0xdf, 0xa1, 0x4a, 0xc6, 0x2c, 0xf4, 0x45,
|
||||
0x25, 0xfc, 0xb3, 0x7f, 0x21, 0x3c, 0x7f, 0x76, 0x7b, 0x29, 0xa1, 0x22, 0xa3, 0x82, 0xcd, 0xbf,
|
||||
0x34, 0xd4, 0x78, 0x93, 0xbe, 0x5d, 0x26, 0x24, 0x7e, 0x36, 0xa3, 0xd1, 0x2c, 0xf8, 0x4e, 0x98,
|
||||
0x48, 0x15, 0x5e, 0xcb, 0x14, 0x56, 0xce, 0x90, 0x09, 0x7d, 0x03, 0x54, 0x66, 0x12, 0xbc, 0x33,
|
||||
0x71, 0xf7, 0x2f, 0x2c, 0x2e, 0x77, 0xf0, 0xf1, 0x18, 0xfc, 0x3a, 0x29, 0x4e, 0x52, 0x8e, 0xe6,
|
||||
0x2f, 0x1a, 0xba, 0x76, 0x00, 0xc1, 0x21, 0x73, 0x80, 0x40, 0x17, 0x02, 0xf0, 0x1d, 0xc0, 0x16,
|
||||
0x5a, 0x19, 0x8d, 0x88, 0xcc, 0x19, 0x36, 0xb2, 0xec, 0x95, 0xd1, 0x38, 0x21, 0xe3, 0x3d, 0x23,
|
||||
0x17, 0x59, 0x3c, 0xd7, 0x45, 0xae, 0xa3, 0xa5, 0x21, 0x95, 0x7d, 0xbd, 0xa4, 0x76, 0x54, 0x92,
|
||||
0xe8, 0x43, 0x2a, 0xfb, 0x44, 0xa1, 0x2a, 0xca, 0x03, 0xa9, 0x66, 0x70, 0x39, 0x8b, 0xf2, 0x40,
|
||||
0x12, 0x85, 0x36, 0x4f, 0x96, 0xd1, 0xc6, 0x13, 0xea, 0xb2, 0xce, 0x95, 0x73, 0x5d, 0x39, 0xd7,
|
||||
0xdb, 0x9d, 0x0b, 0x5d, 0x39, 0xd7, 0x85, 0x9c, 0x6b, 0x8e, 0xaf, 0x54, 0x2f, 0xcd, 0x57, 0x4e,
|
||||
0x34, 0x54, 0x9f, 0x79, 0xe3, 0x97, 0xed, 0x2c, 0xdf, 0xcf, 0x38, 0xcb, 0xe7, 0xc5, 0xa5, 0xcf,
|
||||
0x9c, 0x7e, 0xc6, 0x5b, 0xfe, 0xd6, 0x50, 0xf3, 0xcd, 0x1a, 0x2f, 0xc1, 0x5d, 0xbc, 0xbc, 0xbb,
|
||||
0x7c, 0xf5, 0x1f, 0x04, 0x16, 0xf1, 0x97, 0x5f, 0x35, 0xf4, 0xff, 0x39, 0x63, 0x14, 0xbf, 0x8f,
|
||||
0x4a, 0x61, 0xe0, 0x66, 0x76, 0xb0, 0x1c, 0x47, 0x46, 0xe9, 0x31, 0xd9, 0x25, 0x09, 0x86, 0x29,
|
||||
0x5a, 0x16, 0xa9, 0x23, 0x65, 0xf2, 0xef, 0x14, 0x3f, 0xe3, 0xb4, 0x95, 0xd9, 0xd5, 0x38, 0x32,
|
||||
0x96, 0xcf, 0xd0, 0xb3, 0xba, 0xb8, 0x85, 0x2a, 0x0e, 0xb5, 0x43, 0xbf, 0xe3, 0xa6, 0x9e, 0x55,
|
||||
0xb3, 0x6b, 0xc9, 0x75, 0xed, 0xdc, 0x4d, 0x31, 0x32, 0x8a, 0xda, 0xfb, 0xc7, 0x27, 0xf5, 0x85,
|
||||
0x57, 0x27, 0xf5, 0x85, 0xd7, 0x27, 0xf5, 0x85, 0x9f, 0xe2, 0xba, 0x76, 0x1c, 0xd7, 0xb5, 0x57,
|
||||
0x71, 0x5d, 0x7b, 0x1d, 0xd7, 0xb5, 0x3f, 0xe2, 0xba, 0xf6, 0xf3, 0x9f, 0xf5, 0x85, 0x6f, 0x5b,
|
||||
0x45, 0xff, 0x28, 0xff, 0x13, 0x00, 0x00, 0xff, 0xff, 0x1f, 0xf5, 0x97, 0x1c, 0x6c, 0x0f, 0x00,
|
||||
0x00,
|
||||
}
|
||||
|
||||
func (m *MatchCondition) Marshal() (dAtA []byte, err error) {
|
||||
size := m.Size()
|
||||
dAtA = make([]byte, size)
|
||||
n, err := m.MarshalToSizedBuffer(dAtA[:size])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return dAtA[:n], nil
|
||||
}
|
||||
|
||||
func (m *MatchCondition) MarshalTo(dAtA []byte) (int, error) {
|
||||
size := m.Size()
|
||||
return m.MarshalToSizedBuffer(dAtA[:size])
|
||||
}
|
||||
|
||||
func (m *MatchCondition) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
i := len(dAtA)
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
i -= len(m.Expression)
|
||||
copy(dAtA[i:], m.Expression)
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Expression)))
|
||||
i--
|
||||
dAtA[i] = 0x12
|
||||
i -= len(m.Name)
|
||||
copy(dAtA[i:], m.Name)
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name)))
|
||||
i--
|
||||
dAtA[i] = 0xa
|
||||
return len(dAtA) - i, nil
|
||||
}
|
||||
|
||||
func (m *MutatingWebhook) Marshal() (dAtA []byte, err error) {
|
||||
@ -369,6 +436,20 @@ func (m *MutatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.MatchConditions) > 0 {
|
||||
for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x62
|
||||
}
|
||||
}
|
||||
if m.ObjectSelector != nil {
|
||||
{
|
||||
size, err := m.ObjectSelector.MarshalToSizedBuffer(dAtA[:i])
|
||||
@ -626,6 +707,20 @@ func (m *ValidatingWebhook) MarshalToSizedBuffer(dAtA []byte) (int, error) {
|
||||
_ = i
|
||||
var l int
|
||||
_ = l
|
||||
if len(m.MatchConditions) > 0 {
|
||||
for iNdEx := len(m.MatchConditions) - 1; iNdEx >= 0; iNdEx-- {
|
||||
{
|
||||
size, err := m.MatchConditions[iNdEx].MarshalToSizedBuffer(dAtA[:i])
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
i -= size
|
||||
i = encodeVarintGenerated(dAtA, i, uint64(size))
|
||||
}
|
||||
i--
|
||||
dAtA[i] = 0x5a
|
||||
}
|
||||
}
|
||||
if m.ObjectSelector != nil {
|
||||
{
|
||||
size, err := m.ObjectSelector.MarshalToSizedBuffer(dAtA[:i])
|
||||
@ -871,6 +966,19 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int {
|
||||
dAtA[offset] = uint8(v)
|
||||
return base
|
||||
}
|
||||
func (m *MatchCondition) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
}
|
||||
var l int
|
||||
_ = l
|
||||
l = len(m.Name)
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
l = len(m.Expression)
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
return n
|
||||
}
|
||||
|
||||
func (m *MutatingWebhook) Size() (n int) {
|
||||
if m == nil {
|
||||
return 0
|
||||
@ -920,6 +1028,12 @@ func (m *MutatingWebhook) Size() (n int) {
|
||||
l = m.ObjectSelector.Size()
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
if len(m.MatchConditions) > 0 {
|
||||
for _, e := range m.MatchConditions {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -1022,6 +1136,12 @@ func (m *ValidatingWebhook) Size() (n int) {
|
||||
l = m.ObjectSelector.Size()
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
if len(m.MatchConditions) > 0 {
|
||||
for _, e := range m.MatchConditions {
|
||||
l = e.Size()
|
||||
n += 1 + l + sovGenerated(uint64(l))
|
||||
}
|
||||
}
|
||||
return n
|
||||
}
|
||||
|
||||
@ -1086,6 +1206,17 @@ func sovGenerated(x uint64) (n int) {
|
||||
func sozGenerated(x uint64) (n int) {
|
||||
return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63))))
|
||||
}
|
||||
func (this *MatchCondition) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
}
|
||||
s := strings.Join([]string{`&MatchCondition{`,
|
||||
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
|
||||
`Expression:` + fmt.Sprintf("%v", this.Expression) + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
}
|
||||
func (this *MutatingWebhook) String() string {
|
||||
if this == nil {
|
||||
return "nil"
|
||||
@ -1095,6 +1226,11 @@ func (this *MutatingWebhook) String() string {
|
||||
repeatedStringForRules += fmt.Sprintf("%v", f) + ","
|
||||
}
|
||||
repeatedStringForRules += "}"
|
||||
repeatedStringForMatchConditions := "[]MatchCondition{"
|
||||
for _, f := range this.MatchConditions {
|
||||
repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForMatchConditions += "}"
|
||||
s := strings.Join([]string{`&MutatingWebhook{`,
|
||||
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
|
||||
`ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`,
|
||||
@ -1107,6 +1243,7 @@ func (this *MutatingWebhook) String() string {
|
||||
`MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`,
|
||||
`ReinvocationPolicy:` + valueToStringGenerated(this.ReinvocationPolicy) + `,`,
|
||||
`ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v11.LabelSelector", 1) + `,`,
|
||||
`MatchConditions:` + repeatedStringForMatchConditions + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
@ -1165,6 +1302,11 @@ func (this *ValidatingWebhook) String() string {
|
||||
repeatedStringForRules += fmt.Sprintf("%v", f) + ","
|
||||
}
|
||||
repeatedStringForRules += "}"
|
||||
repeatedStringForMatchConditions := "[]MatchCondition{"
|
||||
for _, f := range this.MatchConditions {
|
||||
repeatedStringForMatchConditions += strings.Replace(strings.Replace(f.String(), "MatchCondition", "MatchCondition", 1), `&`, ``, 1) + ","
|
||||
}
|
||||
repeatedStringForMatchConditions += "}"
|
||||
s := strings.Join([]string{`&ValidatingWebhook{`,
|
||||
`Name:` + fmt.Sprintf("%v", this.Name) + `,`,
|
||||
`ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`,
|
||||
@ -1176,6 +1318,7 @@ func (this *ValidatingWebhook) String() string {
|
||||
`AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`,
|
||||
`MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`,
|
||||
`ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "v11.LabelSelector", 1) + `,`,
|
||||
`MatchConditions:` + repeatedStringForMatchConditions + `,`,
|
||||
`}`,
|
||||
}, "")
|
||||
return s
|
||||
@ -1232,6 +1375,120 @@ func valueToStringGenerated(v interface{}) string {
|
||||
pv := reflect.Indirect(rv).Interface()
|
||||
return fmt.Sprintf("*%v", pv)
|
||||
}
|
||||
func (m *MatchCondition) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
for iNdEx < l {
|
||||
preIndex := iNdEx
|
||||
var wire uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
wire |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
fieldNum := int32(wire >> 3)
|
||||
wireType := int(wire & 0x7)
|
||||
if wireType == 4 {
|
||||
return fmt.Errorf("proto: MatchCondition: wiretype end group for non-group")
|
||||
}
|
||||
if fieldNum <= 0 {
|
||||
return fmt.Errorf("proto: MatchCondition: illegal tag %d (wire type %d)", fieldNum, wire)
|
||||
}
|
||||
switch fieldNum {
|
||||
case 1:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Name", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Name = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
case 2:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field Expression", wireType)
|
||||
}
|
||||
var stringLen uint64
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
stringLen |= uint64(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
intStringLen := int(stringLen)
|
||||
if intStringLen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + intStringLen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.Expression = string(dAtA[iNdEx:postIndex])
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if (skippy < 0) || (iNdEx+skippy) < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if (iNdEx + skippy) > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
iNdEx += skippy
|
||||
}
|
||||
}
|
||||
|
||||
if iNdEx > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (m *MutatingWebhook) Unmarshal(dAtA []byte) error {
|
||||
l := len(dAtA)
|
||||
iNdEx := 0
|
||||
@ -1616,6 +1873,40 @@ func (m *MutatingWebhook) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 12:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MatchConditions", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.MatchConditions = append(m.MatchConditions, MatchCondition{})
|
||||
if err := m.MatchConditions[len(m.MatchConditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
@ -2389,6 +2680,40 @@ func (m *ValidatingWebhook) Unmarshal(dAtA []byte) error {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
case 11:
|
||||
if wireType != 2 {
|
||||
return fmt.Errorf("proto: wrong wireType = %d for field MatchConditions", wireType)
|
||||
}
|
||||
var msglen int
|
||||
for shift := uint(0); ; shift += 7 {
|
||||
if shift >= 64 {
|
||||
return ErrIntOverflowGenerated
|
||||
}
|
||||
if iNdEx >= l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
b := dAtA[iNdEx]
|
||||
iNdEx++
|
||||
msglen |= int(b&0x7F) << shift
|
||||
if b < 0x80 {
|
||||
break
|
||||
}
|
||||
}
|
||||
if msglen < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
postIndex := iNdEx + msglen
|
||||
if postIndex < 0 {
|
||||
return ErrInvalidLengthGenerated
|
||||
}
|
||||
if postIndex > l {
|
||||
return io.ErrUnexpectedEOF
|
||||
}
|
||||
m.MatchConditions = append(m.MatchConditions, MatchCondition{})
|
||||
if err := m.MatchConditions[len(m.MatchConditions)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil {
|
||||
return err
|
||||
}
|
||||
iNdEx = postIndex
|
||||
default:
|
||||
iNdEx = preIndex
|
||||
skippy, err := skipGenerated(dAtA[iNdEx:])
|
||||
|
@ -29,6 +29,35 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
|
||||
// Package-wide variables from generator "generated".
|
||||
option go_package = "k8s.io/api/admissionregistration/v1beta1";
|
||||
|
||||
// MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook.
|
||||
message MatchCondition {
|
||||
// Name is an identifier for this match condition, used for strategic merging of MatchConditions,
|
||||
// as well as providing an identifier for logging purposes. A good name should be descriptive of
|
||||
// the associated expression.
|
||||
// Name must be a qualified name consisting 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]') with an
|
||||
// optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')
|
||||
//
|
||||
// Required.
|
||||
optional string name = 1;
|
||||
|
||||
// Expression represents the expression which will be evaluated by CEL. Must evaluate to bool.
|
||||
// CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:
|
||||
//
|
||||
// 'object' - The object from the incoming request. The value is null for DELETE requests.
|
||||
// 'oldObject' - The existing object. The value is null for CREATE requests.
|
||||
// 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest).
|
||||
// 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
|
||||
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
|
||||
// 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
|
||||
// request resource.
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// Required.
|
||||
optional string expression = 2;
|
||||
}
|
||||
|
||||
// MutatingWebhook describes an admission webhook and the resources and operations it applies to.
|
||||
message MutatingWebhook {
|
||||
// The name of the admission webhook.
|
||||
@ -177,6 +206,28 @@ message MutatingWebhook {
|
||||
// Defaults to "Never".
|
||||
// +optional
|
||||
optional string reinvocationPolicy = 10;
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
repeated MatchCondition matchConditions = 12;
|
||||
}
|
||||
|
||||
// MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.
|
||||
@ -356,6 +407,28 @@ message ValidatingWebhook {
|
||||
// Default to `['v1beta1']`.
|
||||
// +optional
|
||||
repeated string admissionReviewVersions = 8;
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
repeated MatchCondition matchConditions = 11;
|
||||
}
|
||||
|
||||
// ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.
|
||||
|
@ -283,6 +283,28 @@ type ValidatingWebhook struct {
|
||||
// Default to `['v1beta1']`.
|
||||
// +optional
|
||||
AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty" protobuf:"bytes,8,rep,name=admissionReviewVersions"`
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,11,rep,name=matchConditions"`
|
||||
}
|
||||
|
||||
// MutatingWebhook describes an admission webhook and the resources and operations it applies to.
|
||||
@ -433,6 +455,28 @@ type MutatingWebhook struct {
|
||||
// Defaults to "Never".
|
||||
// +optional
|
||||
ReinvocationPolicy *ReinvocationPolicyType `json:"reinvocationPolicy,omitempty" protobuf:"bytes,10,opt,name=reinvocationPolicy,casttype=ReinvocationPolicyType"`
|
||||
|
||||
// MatchConditions is a list of conditions that must be met for a request to be sent to this
|
||||
// webhook. Match conditions filter requests that have already been matched by the rules,
|
||||
// namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests.
|
||||
// There are a maximum of 64 match conditions allowed.
|
||||
//
|
||||
// The exact matching logic is (in order):
|
||||
// 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.
|
||||
// 2. If ALL matchConditions evaluate to TRUE, the webhook is called.
|
||||
// 3. If any matchCondition evaluates to an error (but none are FALSE):
|
||||
// - If failurePolicy=Fail, reject the request
|
||||
// - If failurePolicy=Ignore, the error is ignored and the webhook is skipped
|
||||
//
|
||||
// This is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.
|
||||
//
|
||||
// +patchMergeKey=name
|
||||
// +patchStrategy=merge
|
||||
// +listType=map
|
||||
// +listMapKey=name
|
||||
// +featureGate=AdmissionWebhookMatchConditions
|
||||
// +optional
|
||||
MatchConditions []MatchCondition `json:"matchConditions,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,12,rep,name=matchConditions"`
|
||||
}
|
||||
|
||||
// ReinvocationPolicyType specifies what type of policy the admission hook uses.
|
||||
@ -531,3 +575,32 @@ type ServiceReference struct {
|
||||
// +optional
|
||||
Port *int32 `json:"port,omitempty" protobuf:"varint,4,opt,name=port"`
|
||||
}
|
||||
|
||||
// MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook.
|
||||
type MatchCondition struct {
|
||||
// Name is an identifier for this match condition, used for strategic merging of MatchConditions,
|
||||
// as well as providing an identifier for logging purposes. A good name should be descriptive of
|
||||
// the associated expression.
|
||||
// Name must be a qualified name consisting 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]') with an
|
||||
// optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')
|
||||
//
|
||||
// Required.
|
||||
Name string `json:"name" protobuf:"bytes,1,opt,name=name"`
|
||||
|
||||
// Expression represents the expression which will be evaluated by CEL. Must evaluate to bool.
|
||||
// CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:
|
||||
//
|
||||
// 'object' - The object from the incoming request. The value is null for DELETE requests.
|
||||
// 'oldObject' - The existing object. The value is null for CREATE requests.
|
||||
// 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest).
|
||||
// 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.
|
||||
// See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz
|
||||
// 'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the
|
||||
// request resource.
|
||||
// Documentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/
|
||||
//
|
||||
// Required.
|
||||
Expression string `json:"expression" protobuf:"bytes,2,opt,name=expression"`
|
||||
}
|
||||
|
@ -27,6 +27,16 @@ package v1beta1
|
||||
// Those methods can be generated by using hack/update-codegen.sh
|
||||
|
||||
// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT.
|
||||
var map_MatchCondition = map[string]string{
|
||||
"": "MatchCondition represents a condition which must be fulfilled for a request to be sent to a webhook.",
|
||||
"name": "Name is an identifier for this match condition, used for strategic merging of MatchConditions, as well as providing an identifier for logging purposes. A good name should be descriptive of the associated expression. Name must be a qualified name consisting 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]') with an optional DNS subdomain prefix and '/' (e.g. 'example.com/MyName')\n\nRequired.",
|
||||
"expression": "Expression represents the expression which will be evaluated by CEL. Must evaluate to bool. CEL expressions have access to the contents of the AdmissionRequest and Authorizer, organized into CEL variables:\n\n'object' - The object from the incoming request. The value is null for DELETE requests. 'oldObject' - The existing object. The value is null for CREATE requests. 'request' - Attributes of the admission request(/pkg/apis/admission/types.go#AdmissionRequest). 'authorizer' - A CEL Authorizer. May be used to perform authorization checks for the principal (user or service account) of the request.\n See https://pkg.go.dev/k8s.io/apiserver/pkg/cel/library#Authz\n'authorizer.requestResource' - A CEL ResourceCheck constructed from the 'authorizer' and configured with the\n request resource.\nDocumentation on CEL: https://kubernetes.io/docs/reference/using-api/cel/\n\nRequired.",
|
||||
}
|
||||
|
||||
func (MatchCondition) SwaggerDoc() map[string]string {
|
||||
return map_MatchCondition
|
||||
}
|
||||
|
||||
var map_MutatingWebhook = map[string]string{
|
||||
"": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.",
|
||||
"name": "The name of the admission webhook. Name should be fully qualified, e.g., imagepolicy.kubernetes.io, where \"imagepolicy\" is the name of the webhook, and kubernetes.io is the name of the organization. Required.",
|
||||
@ -40,6 +50,7 @@ var map_MutatingWebhook = map[string]string{
|
||||
"timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.",
|
||||
"admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.",
|
||||
"reinvocationPolicy": "reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. Allowed values are \"Never\" and \"IfNeeded\".\n\nNever: the webhook will not be called more than once in a single admission evaluation.\n\nIfNeeded: the webhook will be called at least one additional time as part of the admission evaluation if the object being admitted is modified by other admission plugins after the initial webhook call. Webhooks that specify this option *must* be idempotent, able to process objects they previously admitted. Note: * the number of additional invocations is not guaranteed to be exactly one. * if additional invocations result in further modifications to the object, webhooks are not guaranteed to be invoked again. * webhooks that use this option may be reordered to minimize the number of additional invocations. * to validate an object after all mutations are guaranteed complete, use a validating admission webhook instead.\n\nDefaults to \"Never\".",
|
||||
"matchConditions": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
}
|
||||
|
||||
func (MutatingWebhook) SwaggerDoc() map[string]string {
|
||||
@ -90,6 +101,7 @@ var map_ValidatingWebhook = map[string]string{
|
||||
"sideEffects": "SideEffects states whether this webhook has side effects. Acceptable values are: Unknown, None, Some, NoneOnDryRun Webhooks with side effects MUST implement a reconciliation system, since a request may be rejected by a future step in the admission chain and the side effects therefore need to be undone. Requests with the dryRun attribute will be auto-rejected if they match a webhook with sideEffects == Unknown or Some. Defaults to Unknown.",
|
||||
"timeoutSeconds": "TimeoutSeconds specifies the timeout for this webhook. After the timeout passes, the webhook call will be ignored or the API call will fail based on the failure policy. The timeout value must be between 1 and 30 seconds. Default to 30 seconds.",
|
||||
"admissionReviewVersions": "AdmissionReviewVersions is an ordered list of preferred `AdmissionReview` versions the Webhook expects. API server will try to use first version in the list which it supports. If none of the versions specified in this list supported by API server, validation will fail for this object. If a persisted webhook configuration specifies allowed versions and does not include any versions known to the API Server, calls to the webhook will fail and be subject to the failure policy. Default to `['v1beta1']`.",
|
||||
"matchConditions": "MatchConditions is a list of conditions that must be met for a request to be sent to this webhook. Match conditions filter requests that have already been matched by the rules, namespaceSelector, and objectSelector. An empty list of matchConditions matches all requests. There are a maximum of 64 match conditions allowed.\n\nThe exact matching logic is (in order):\n 1. If ANY matchCondition evaluates to FALSE, the webhook is skipped.\n 2. If ALL matchConditions evaluate to TRUE, the webhook is called.\n 3. If any matchCondition evaluates to an error (but none are FALSE):\n - If failurePolicy=Fail, reject the request\n - If failurePolicy=Ignore, the error is ignored and the webhook is skipped\n\nThis is an alpha feature and managed by the AdmissionWebhookMatchConditions feature gate.",
|
||||
}
|
||||
|
||||
func (ValidatingWebhook) SwaggerDoc() map[string]string {
|
||||
|
@ -27,6 +27,22 @@ import (
|
||||
runtime "k8s.io/apimachinery/pkg/runtime"
|
||||
)
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MatchCondition) DeepCopyInto(out *MatchCondition) {
|
||||
*out = *in
|
||||
return
|
||||
}
|
||||
|
||||
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MatchCondition.
|
||||
func (in *MatchCondition) DeepCopy() *MatchCondition {
|
||||
if in == nil {
|
||||
return nil
|
||||
}
|
||||
out := new(MatchCondition)
|
||||
in.DeepCopyInto(out)
|
||||
return out
|
||||
}
|
||||
|
||||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *MutatingWebhook) DeepCopyInto(out *MutatingWebhook) {
|
||||
*out = *in
|
||||
@ -78,6 +94,11 @@ func (in *MutatingWebhook) DeepCopyInto(out *MutatingWebhook) {
|
||||
*out = new(ReinvocationPolicyType)
|
||||
**out = **in
|
||||
}
|
||||
if in.MatchConditions != nil {
|
||||
in, out := &in.MatchConditions, &out.MatchConditions
|
||||
*out = make([]MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
@ -229,6 +250,11 @@ func (in *ValidatingWebhook) DeepCopyInto(out *ValidatingWebhook) {
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.MatchConditions != nil {
|
||||
in, out := &in.MatchConditions, &out.MatchConditions
|
||||
*out = make([]MatchCondition, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
|
@ -108,7 +108,13 @@
|
||||
"admissionReviewVersions": [
|
||||
"admissionReviewVersionsValue"
|
||||
],
|
||||
"reinvocationPolicy": "reinvocationPolicyValue"
|
||||
"reinvocationPolicy": "reinvocationPolicyValue",
|
||||
"matchConditions": [
|
||||
{
|
||||
"name": "nameValue",
|
||||
"expression": "expressionValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
@ -44,6 +44,9 @@ webhooks:
|
||||
port: 4
|
||||
url: urlValue
|
||||
failurePolicy: failurePolicyValue
|
||||
matchConditions:
|
||||
- expression: expressionValue
|
||||
name: nameValue
|
||||
matchPolicy: matchPolicyValue
|
||||
name: nameValue
|
||||
namespaceSelector:
|
||||
|
@ -107,6 +107,12 @@
|
||||
"timeoutSeconds": 7,
|
||||
"admissionReviewVersions": [
|
||||
"admissionReviewVersionsValue"
|
||||
],
|
||||
"matchConditions": [
|
||||
{
|
||||
"name": "nameValue",
|
||||
"expression": "expressionValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
Binary file not shown.
@ -44,6 +44,9 @@ webhooks:
|
||||
port: 4
|
||||
url: urlValue
|
||||
failurePolicy: failurePolicyValue
|
||||
matchConditions:
|
||||
- expression: expressionValue
|
||||
name: nameValue
|
||||
matchPolicy: matchPolicyValue
|
||||
name: nameValue
|
||||
namespaceSelector:
|
||||
|
@ -108,7 +108,13 @@
|
||||
"admissionReviewVersions": [
|
||||
"admissionReviewVersionsValue"
|
||||
],
|
||||
"reinvocationPolicy": "reinvocationPolicyValue"
|
||||
"reinvocationPolicy": "reinvocationPolicyValue",
|
||||
"matchConditions": [
|
||||
{
|
||||
"name": "nameValue",
|
||||
"expression": "expressionValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
Binary file not shown.
@ -44,6 +44,9 @@ webhooks:
|
||||
port: 4
|
||||
url: urlValue
|
||||
failurePolicy: failurePolicyValue
|
||||
matchConditions:
|
||||
- expression: expressionValue
|
||||
name: nameValue
|
||||
matchPolicy: matchPolicyValue
|
||||
name: nameValue
|
||||
namespaceSelector:
|
||||
|
@ -107,6 +107,12 @@
|
||||
"timeoutSeconds": 7,
|
||||
"admissionReviewVersions": [
|
||||
"admissionReviewVersionsValue"
|
||||
],
|
||||
"matchConditions": [
|
||||
{
|
||||
"name": "nameValue",
|
||||
"expression": "expressionValue"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
Binary file not shown.
@ -44,6 +44,9 @@ webhooks:
|
||||
port: 4
|
||||
url: urlValue
|
||||
failurePolicy: failurePolicyValue
|
||||
matchConditions:
|
||||
- expression: expressionValue
|
||||
name: nameValue
|
||||
matchPolicy: matchPolicyValue
|
||||
name: nameValue
|
||||
namespaceSelector:
|
||||
|
@ -112,12 +112,13 @@ func (p pluginHandlerWithMetrics) Validate(ctx context.Context, a admission.Attr
|
||||
|
||||
// AdmissionMetrics instruments admission with prometheus metrics.
|
||||
type AdmissionMetrics struct {
|
||||
step *metricSet
|
||||
controller *metricSet
|
||||
webhook *metricSet
|
||||
webhookRejection *metrics.CounterVec
|
||||
webhookFailOpen *metrics.CounterVec
|
||||
webhookRequest *metrics.CounterVec
|
||||
step *metricSet
|
||||
controller *metricSet
|
||||
webhook *metricSet
|
||||
webhookRejection *metrics.CounterVec
|
||||
webhookFailOpen *metrics.CounterVec
|
||||
webhookRequest *metrics.CounterVec
|
||||
matchConditionEvalErrors *metrics.CounterVec
|
||||
}
|
||||
|
||||
// newAdmissionMetrics create a new AdmissionMetrics, configured with default metric names.
|
||||
@ -217,13 +218,24 @@ func newAdmissionMetrics() *AdmissionMetrics {
|
||||
},
|
||||
[]string{"name", "type", "operation", "code", "rejected"})
|
||||
|
||||
matchConditionEvalError := metrics.NewCounterVec(
|
||||
&metrics.CounterOpts{
|
||||
Namespace: namespace,
|
||||
Subsystem: subsystem,
|
||||
Name: "admission_match_condition_evaluation_errors_total",
|
||||
Help: "Admission match condition evaluation errors count, identified by name of resource containing the match condition and broken out for each admission type (validating or mutating).",
|
||||
StabilityLevel: metrics.ALPHA,
|
||||
},
|
||||
[]string{"name", "type"})
|
||||
|
||||
step.mustRegister()
|
||||
controller.mustRegister()
|
||||
webhook.mustRegister()
|
||||
legacyregistry.MustRegister(webhookRejection)
|
||||
legacyregistry.MustRegister(webhookFailOpen)
|
||||
legacyregistry.MustRegister(webhookRequest)
|
||||
return &AdmissionMetrics{step: step, controller: controller, webhook: webhook, webhookRejection: webhookRejection, webhookFailOpen: webhookFailOpen, webhookRequest: webhookRequest}
|
||||
legacyregistry.MustRegister(matchConditionEvalError)
|
||||
return &AdmissionMetrics{step: step, controller: controller, webhook: webhook, webhookRejection: webhookRejection, webhookFailOpen: webhookFailOpen, webhookRequest: webhookRequest, matchConditionEvalErrors: matchConditionEvalError}
|
||||
}
|
||||
|
||||
func (m *AdmissionMetrics) reset() {
|
||||
@ -267,6 +279,11 @@ func (m *AdmissionMetrics) ObserveWebhookFailOpen(ctx context.Context, name, ste
|
||||
m.webhookFailOpen.WithContext(ctx).WithLabelValues(name, stepType).Inc()
|
||||
}
|
||||
|
||||
// ObserveMatchConditionEvalError records validating or mutating webhook that are not called due to match conditions
|
||||
func (m *AdmissionMetrics) ObserveMatchConditionEvalError(ctx context.Context, name, stepType string) {
|
||||
m.matchConditionEvalErrors.WithContext(ctx).WithLabelValues(name, stepType).Inc()
|
||||
}
|
||||
|
||||
type metricSet struct {
|
||||
latencies *metrics.HistogramVec
|
||||
latenciesSummary *metrics.SummaryVec
|
||||
|
@ -29,8 +29,6 @@ import (
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
var _ ExpressionAccessor = &MatchCondition{}
|
||||
|
||||
type ExpressionAccessor interface {
|
||||
GetExpression() string
|
||||
ReturnTypes() []*cel.Type
|
||||
@ -44,19 +42,6 @@ type EvaluationResult struct {
|
||||
Error error
|
||||
}
|
||||
|
||||
// MatchCondition contains the inputs needed to compile, evaluate and match a cel expression
|
||||
type MatchCondition struct {
|
||||
Expression string
|
||||
}
|
||||
|
||||
func (v *MatchCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *MatchCondition) ReturnTypes() []*cel.Type {
|
||||
return []*cel.Type{cel.BoolType}
|
||||
}
|
||||
|
||||
// OptionalVariableDeclarations declares which optional CEL variables
|
||||
// are declared for an expression.
|
||||
type OptionalVariableDeclarations struct {
|
||||
|
@ -23,8 +23,6 @@ import (
|
||||
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
@ -33,6 +31,7 @@ import (
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
apiservercel "k8s.io/apiserver/pkg/cel"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// validator implements the Validator interface
|
||||
|
@ -19,11 +19,15 @@ package webhook
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/rest"
|
||||
)
|
||||
@ -44,6 +48,9 @@ type WebhookAccessor interface {
|
||||
// GetRESTClient gets the webhook client
|
||||
GetRESTClient(clientManager *webhookutil.ClientManager) (*rest.RESTClient, error)
|
||||
|
||||
// GetCompiledMatcher gets the compiled matcher object
|
||||
GetCompiledMatcher(compiler cel.FilterCompiler, authorizer authorizer.Authorizer) matchconditions.Matcher
|
||||
|
||||
// GetName gets the webhook Name field. Note that the name is scoped to the webhook
|
||||
// configuration and does not provide a globally unique identity, if a unique identity is
|
||||
// needed, use GetUID.
|
||||
@ -67,6 +74,9 @@ type WebhookAccessor interface {
|
||||
// GetAdmissionReviewVersions gets the webhook AdmissionReviewVersions field.
|
||||
GetAdmissionReviewVersions() []string
|
||||
|
||||
// GetMatchConditions gets the webhook match conditions field.
|
||||
GetMatchConditions() []v1.MatchCondition
|
||||
|
||||
// GetMutatingWebhook if the accessor contains a MutatingWebhook, returns it and true, else returns false.
|
||||
GetMutatingWebhook() (*v1.MutatingWebhook, bool)
|
||||
// GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false.
|
||||
@ -94,6 +104,9 @@ type mutatingWebhookAccessor struct {
|
||||
initClient sync.Once
|
||||
client *rest.RESTClient
|
||||
clientErr error
|
||||
|
||||
compileMatcher sync.Once
|
||||
compiledMatcher matchconditions.Matcher
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetUID() string {
|
||||
@ -111,6 +124,28 @@ func (m *mutatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.Clien
|
||||
return m.client, m.clientErr
|
||||
}
|
||||
|
||||
// TODO: graduation to beta: resolve the fact that we rebuild ALL items whenever ANY config changes in NewMutatingWebhookConfigurationManager and NewValidatingWebhookConfigurationManager ... now that we're doing CEL compilation, we probably want to avoid that
|
||||
func (m *mutatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler, authorizer authorizer.Authorizer) matchconditions.Matcher {
|
||||
m.compileMatcher.Do(func() {
|
||||
expressions := make([]cel.ExpressionAccessor, len(m.MutatingWebhook.MatchConditions))
|
||||
for i, matchCondition := range m.MutatingWebhook.MatchConditions {
|
||||
expressions[i] = &matchconditions.MatchCondition{
|
||||
Name: matchCondition.Name,
|
||||
Expression: matchCondition.Expression,
|
||||
}
|
||||
}
|
||||
m.compiledMatcher = matchconditions.NewMatcher(compiler.Compile(
|
||||
expressions,
|
||||
cel.OptionalVariableDeclarations{
|
||||
HasParams: false,
|
||||
HasAuthorizer: true,
|
||||
},
|
||||
celconfig.PerCallLimit,
|
||||
), authorizer, m.FailurePolicy, "validating", m.Name)
|
||||
})
|
||||
return m.compiledMatcher
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
m.initNamespaceSelector.Do(func() {
|
||||
m.namespaceSelector, m.namespaceSelectorErr = metav1.LabelSelectorAsSelector(m.NamespaceSelector)
|
||||
@ -165,6 +200,10 @@ func (m *mutatingWebhookAccessor) GetAdmissionReviewVersions() []string {
|
||||
return m.AdmissionReviewVersions
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMatchConditions() []v1.MatchCondition {
|
||||
return m.MatchConditions
|
||||
}
|
||||
|
||||
func (m *mutatingWebhookAccessor) GetMutatingWebhook() (*v1.MutatingWebhook, bool) {
|
||||
return m.MutatingWebhook, true
|
||||
}
|
||||
@ -194,6 +233,9 @@ type validatingWebhookAccessor struct {
|
||||
initClient sync.Once
|
||||
client *rest.RESTClient
|
||||
clientErr error
|
||||
|
||||
compileMatcher sync.Once
|
||||
compiledMatcher matchconditions.Matcher
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetUID() string {
|
||||
@ -211,6 +253,27 @@ func (v *validatingWebhookAccessor) GetRESTClient(clientManager *webhookutil.Cli
|
||||
return v.client, v.clientErr
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler, authorizer authorizer.Authorizer) matchconditions.Matcher {
|
||||
v.compileMatcher.Do(func() {
|
||||
expressions := make([]cel.ExpressionAccessor, len(v.ValidatingWebhook.MatchConditions))
|
||||
for i, matchCondition := range v.ValidatingWebhook.MatchConditions {
|
||||
expressions[i] = &matchconditions.MatchCondition{
|
||||
Name: matchCondition.Name,
|
||||
Expression: matchCondition.Expression,
|
||||
}
|
||||
}
|
||||
v.compiledMatcher = matchconditions.NewMatcher(compiler.Compile(
|
||||
expressions,
|
||||
cel.OptionalVariableDeclarations{
|
||||
HasParams: false,
|
||||
HasAuthorizer: true,
|
||||
},
|
||||
celconfig.PerCallLimit,
|
||||
), authorizer, v.FailurePolicy, "validating", v.Name)
|
||||
})
|
||||
return v.compiledMatcher
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetParsedNamespaceSelector() (labels.Selector, error) {
|
||||
v.initNamespaceSelector.Do(func() {
|
||||
v.namespaceSelector, v.namespaceSelectorErr = metav1.LabelSelectorAsSelector(v.NamespaceSelector)
|
||||
@ -265,6 +328,10 @@ func (v *validatingWebhookAccessor) GetAdmissionReviewVersions() []string {
|
||||
return v.AdmissionReviewVersions
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMatchConditions() []v1.MatchCondition {
|
||||
return v.MatchConditions
|
||||
}
|
||||
|
||||
func (v *validatingWebhookAccessor) GetMutatingWebhook() (*v1.MutatingWebhook, bool) {
|
||||
return nil, false
|
||||
}
|
||||
|
@ -62,6 +62,7 @@ func TestMutatingWebhookAccessor(t *testing.T) {
|
||||
SideEffects: accessor.GetSideEffects(),
|
||||
TimeoutSeconds: accessor.GetTimeoutSeconds(),
|
||||
AdmissionReviewVersions: accessor.GetAdmissionReviewVersions(),
|
||||
MatchConditions: accessor.GetMatchConditions(),
|
||||
}
|
||||
if !reflect.DeepEqual(orig, copy) {
|
||||
t.Errorf("expected mutatingWebhook to round trip through WebhookAccessor, diff:\n%s", diff.ObjectReflectDiff(orig, copy))
|
||||
@ -102,6 +103,7 @@ func TestValidatingWebhookAccessor(t *testing.T) {
|
||||
SideEffects: accessor.GetSideEffects(),
|
||||
TimeoutSeconds: accessor.GetTimeoutSeconds(),
|
||||
AdmissionReviewVersions: accessor.GetAdmissionReviewVersions(),
|
||||
MatchConditions: accessor.GetMatchConditions(),
|
||||
}
|
||||
if !reflect.DeepEqual(orig, copy) {
|
||||
t.Errorf("expected validatingWebhook to round trip through WebhookAccessor, diff:\n%s", diff.ObjectReflectDiff(orig, copy))
|
||||
|
@ -24,6 +24,10 @@ import (
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
)
|
||||
|
||||
type VersionedAttributeAccessor interface {
|
||||
VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error)
|
||||
}
|
||||
|
||||
// Source can list dynamic webhook plugins.
|
||||
type Source interface {
|
||||
Webhooks() []webhook.WebhookAccessor
|
||||
|
@ -23,19 +23,22 @@ import (
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionv1beta1 "k8s.io/api/admission/v1beta1"
|
||||
"k8s.io/api/admissionregistration/v1"
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/config"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/rules"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/client-go/informers"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// Webhook is an abstract admission plugin with all the infrastructure to define Admit or Validate on-top.
|
||||
@ -49,6 +52,8 @@ type Webhook struct {
|
||||
namespaceMatcher *namespace.Matcher
|
||||
objectMatcher *object.Matcher
|
||||
dispatcher Dispatcher
|
||||
filterCompiler cel.FilterCompiler
|
||||
authorizer authorizer.Authorizer
|
||||
}
|
||||
|
||||
var (
|
||||
@ -92,6 +97,7 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
|
||||
namespaceMatcher: &namespace.Matcher{},
|
||||
objectMatcher: &object.Matcher{},
|
||||
dispatcher: dispatcherFactory(&cm),
|
||||
filterCompiler: cel.NewFilterCompiler(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
@ -124,6 +130,10 @@ func (a *Webhook) SetExternalKubeInformerFactory(f informers.SharedInformerFacto
|
||||
})
|
||||
}
|
||||
|
||||
func (a *Webhook) SetAuthorizer(authorizer authorizer.Authorizer) {
|
||||
a.authorizer = authorizer
|
||||
}
|
||||
|
||||
// ValidateInitialization implements the InitializationValidator interface.
|
||||
func (a *Webhook) ValidateInitialization() error {
|
||||
if a.hookSource == nil {
|
||||
@ -140,7 +150,7 @@ func (a *Webhook) ValidateInitialization() error {
|
||||
|
||||
// ShouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called,
|
||||
// or an error if an error was encountered during evaluation.
|
||||
func (a *Webhook) ShouldCallHook(h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) {
|
||||
func (a *Webhook) ShouldCallHook(ctx context.Context, h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces, v VersionedAttributeAccessor) (*WebhookInvocation, *apierrors.StatusError) {
|
||||
matches, matchNsErr := a.namespaceMatcher.MatchNamespaceSelector(h, attr)
|
||||
// Should not return an error here for webhooks which do not apply to the request, even if err is an unexpected scenario.
|
||||
if !matches && matchNsErr == nil {
|
||||
@ -207,6 +217,25 @@ func (a *Webhook) ShouldCallHook(h webhook.WebhookAccessor, attr admission.Attri
|
||||
return nil, matchObjErr
|
||||
}
|
||||
|
||||
matchConditions := h.GetMatchConditions()
|
||||
if len(matchConditions) > 0 {
|
||||
versionedAttr, err := v.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
matcher := h.GetCompiledMatcher(a.filterCompiler, a.authorizer)
|
||||
matchResult := matcher.Match(ctx, versionedAttr, nil)
|
||||
|
||||
if matchResult.Error != nil {
|
||||
klog.Warningf("Failed evaluating match conditions, failing closed %v: %v", h.GetName(), matchResult.Error)
|
||||
return nil, apierrors.NewForbidden(attr.GetResource().GroupResource(), attr.GetName(), matchResult.Error)
|
||||
} else if !matchResult.Matches {
|
||||
// if no match, always skip webhook
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
|
||||
return invocation, nil
|
||||
}
|
||||
|
||||
|
@ -17,21 +17,26 @@ limitations under the License.
|
||||
package generic
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
k8serrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
)
|
||||
|
||||
func gvr(group, version, resource string) schema.GroupVersionResource {
|
||||
@ -42,8 +47,55 @@ func gvk(group, version, kind string) schema.GroupVersionKind {
|
||||
return schema.GroupVersionKind{Group: group, Version: version, Kind: kind}
|
||||
}
|
||||
|
||||
var _ matchconditions.Matcher = &fakeMatcher{}
|
||||
|
||||
type fakeMatcher struct {
|
||||
throwError error
|
||||
matchResult bool
|
||||
}
|
||||
|
||||
func (f *fakeMatcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object) matchconditions.MatchResult {
|
||||
if f.throwError != nil {
|
||||
return matchconditions.MatchResult{
|
||||
Matches: true,
|
||||
FailedConditionName: "",
|
||||
Error: f.throwError,
|
||||
}
|
||||
}
|
||||
return matchconditions.MatchResult{
|
||||
Matches: f.matchResult,
|
||||
FailedConditionName: "",
|
||||
}
|
||||
}
|
||||
|
||||
var _ webhook.WebhookAccessor = &fakeWebhookAccessor{}
|
||||
|
||||
type fakeWebhookAccessor struct {
|
||||
webhook.WebhookAccessor
|
||||
throwError error
|
||||
matchResult bool
|
||||
}
|
||||
|
||||
func (f *fakeWebhookAccessor) GetCompiledMatcher(compiler cel.FilterCompiler, authorizer authorizer.Authorizer) matchconditions.Matcher {
|
||||
return &fakeMatcher{
|
||||
throwError: f.throwError,
|
||||
matchResult: f.matchResult,
|
||||
}
|
||||
}
|
||||
|
||||
var _ VersionedAttributeAccessor = &fakeVersionedAttributeAccessor{}
|
||||
|
||||
type fakeVersionedAttributeAccessor struct{}
|
||||
|
||||
func (v *fakeVersionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func TestShouldCallHook(t *testing.T) {
|
||||
a := &Webhook{namespaceMatcher: &namespace.Matcher{}, objectMatcher: &object.Matcher{}}
|
||||
a := &Webhook{
|
||||
namespaceMatcher: &namespace.Matcher{},
|
||||
objectMatcher: &object.Matcher{},
|
||||
}
|
||||
|
||||
allScopes := v1.AllScopes
|
||||
exactMatch := v1.Exact
|
||||
@ -83,12 +135,15 @@ func TestShouldCallHook(t *testing.T) {
|
||||
expectCallResource schema.GroupVersionResource
|
||||
expectCallSubresource string
|
||||
expectCallKind schema.GroupVersionKind
|
||||
matchError error
|
||||
matchResult bool
|
||||
}{
|
||||
{
|
||||
name: "no rules (just write)",
|
||||
webhook: &v1.ValidatingWebhook{NamespaceSelector: &metav1.LabelSelector{}, Rules: []v1.RuleWithOperations{}},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
name: "no rules (just write)",
|
||||
webhook: &v1.ValidatingWebhook{NamespaceSelector: &metav1.LabelSelector{}, Rules: []v1.RuleWithOperations{}},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "invalid kind lookup",
|
||||
@ -100,9 +155,10 @@ func TestShouldCallHook(t *testing.T) {
|
||||
Operations: []v1.OperationType{"*"},
|
||||
Rule: v1.Rule{APIGroups: []string{"example.com"}, APIVersions: []string{"v1"}, Resources: []string{"widgets"}, Scope: &allScopes},
|
||||
}}},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("example.com", "v2", "Widget"), "ns", "name", gvr("example.com", "v2", "widgets"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
expectErr: "unknown kind",
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("example.com", "v2", "Widget"), "ns", "name", gvr("example.com", "v2", "widgets"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
expectErr: "unknown kind",
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "wildcard rule, match as requested",
|
||||
@ -118,6 +174,7 @@ func TestShouldCallHook(t *testing.T) {
|
||||
expectCallKind: gvk("apps", "v1", "Deployment"),
|
||||
expectCallResource: gvr("apps", "v1", "deployments"),
|
||||
expectCallSubresource: "",
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, prefer exact match",
|
||||
@ -139,6 +196,7 @@ func TestShouldCallHook(t *testing.T) {
|
||||
expectCallKind: gvk("apps", "v1", "Deployment"),
|
||||
expectCallResource: gvr("apps", "v1", "deployments"),
|
||||
expectCallSubresource: "",
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, match miss",
|
||||
@ -152,8 +210,9 @@ func TestShouldCallHook(t *testing.T) {
|
||||
Operations: []v1.OperationType{"*"},
|
||||
Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
|
||||
}}},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, exact match miss",
|
||||
@ -168,8 +227,9 @@ func TestShouldCallHook(t *testing.T) {
|
||||
Operations: []v1.OperationType{"*"},
|
||||
Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes},
|
||||
}}},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, equivalent match, prefer extensions",
|
||||
@ -189,6 +249,7 @@ func TestShouldCallHook(t *testing.T) {
|
||||
expectCallKind: gvk("extensions", "v1beta1", "Deployment"),
|
||||
expectCallResource: gvr("extensions", "v1beta1", "deployments"),
|
||||
expectCallSubresource: "",
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, equivalent match, prefer apps",
|
||||
@ -208,6 +269,7 @@ func TestShouldCallHook(t *testing.T) {
|
||||
expectCallKind: gvk("apps", "v1beta1", "Deployment"),
|
||||
expectCallResource: gvr("apps", "v1beta1", "deployments"),
|
||||
expectCallSubresource: "",
|
||||
matchResult: true,
|
||||
},
|
||||
|
||||
{
|
||||
@ -230,6 +292,7 @@ func TestShouldCallHook(t *testing.T) {
|
||||
expectCallKind: gvk("autoscaling", "v1", "Scale"),
|
||||
expectCallResource: gvr("apps", "v1", "deployments"),
|
||||
expectCallSubresource: "scale",
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, subresource match miss",
|
||||
@ -243,8 +306,9 @@ func TestShouldCallHook(t *testing.T) {
|
||||
Operations: []v1.OperationType{"*"},
|
||||
Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
|
||||
}}},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, subresource exact match miss",
|
||||
@ -259,8 +323,9 @@ func TestShouldCallHook(t *testing.T) {
|
||||
Operations: []v1.OperationType{"*"},
|
||||
Rule: v1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes},
|
||||
}}},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, subresource equivalent match, prefer extensions",
|
||||
@ -280,6 +345,7 @@ func TestShouldCallHook(t *testing.T) {
|
||||
expectCallKind: gvk("extensions", "v1beta1", "Scale"),
|
||||
expectCallResource: gvr("extensions", "v1beta1", "deployments"),
|
||||
expectCallSubresource: "scale",
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "specific rules, subresource equivalent match, prefer apps",
|
||||
@ -299,12 +365,86 @@ func TestShouldCallHook(t *testing.T) {
|
||||
expectCallKind: gvk("apps", "v1beta1", "Scale"),
|
||||
expectCallResource: gvr("apps", "v1beta1", "deployments"),
|
||||
expectCallSubresource: "scale",
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "wildcard rule, match conditions also match",
|
||||
webhook: &v1.ValidatingWebhook{
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
ObjectSelector: &metav1.LabelSelector{},
|
||||
Rules: []v1.RuleWithOperations{{
|
||||
Operations: []v1.OperationType{"*"},
|
||||
Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes},
|
||||
}},
|
||||
MatchConditions: []v1.MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
Expression: "test expression",
|
||||
},
|
||||
},
|
||||
},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: true,
|
||||
expectCallKind: gvk("apps", "v1", "Deployment"),
|
||||
expectCallResource: gvr("apps", "v1", "deployments"),
|
||||
expectCallSubresource: "",
|
||||
matchResult: true,
|
||||
},
|
||||
{
|
||||
name: "wildcard rule, match conditions do not match",
|
||||
webhook: &v1.ValidatingWebhook{
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
ObjectSelector: &metav1.LabelSelector{},
|
||||
Rules: []v1.RuleWithOperations{{
|
||||
Operations: []v1.OperationType{"*"},
|
||||
Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes},
|
||||
}},
|
||||
MatchConditions: []v1.MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
Expression: "test expression",
|
||||
},
|
||||
},
|
||||
},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
matchResult: false,
|
||||
},
|
||||
{
|
||||
name: "wildcard rule, match conditions error",
|
||||
webhook: &v1.ValidatingWebhook{
|
||||
NamespaceSelector: &metav1.LabelSelector{},
|
||||
ObjectSelector: &metav1.LabelSelector{},
|
||||
Rules: []v1.RuleWithOperations{{
|
||||
Operations: []v1.OperationType{"*"},
|
||||
Rule: v1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes},
|
||||
}},
|
||||
MatchConditions: []v1.MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
Expression: "test expression",
|
||||
},
|
||||
},
|
||||
},
|
||||
attrs: admission.NewAttributesRecord(nil, nil, gvk("apps", "v1", "Deployment"), "ns", "name", gvr("apps", "v1", "deployments"), "", admission.Create, &metav1.CreateOptions{}, false, nil),
|
||||
expectCall: false,
|
||||
expectErr: "deployments.apps \"name\" is forbidden: test error",
|
||||
expectCallKind: gvk("apps", "v1", "Deployment"),
|
||||
expectCallResource: gvr("apps", "v1", "deployments"),
|
||||
expectCallSubresource: "",
|
||||
matchError: errors.New("test error"),
|
||||
},
|
||||
}
|
||||
|
||||
for i, testcase := range testcases {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
invocation, err := a.ShouldCallHook(webhook.NewValidatingWebhookAccessor(fmt.Sprintf("webhook-%d", i), fmt.Sprintf("webhook-cfg-%d", i), testcase.webhook), testcase.attrs, interfaces)
|
||||
fakeWebhook := &fakeWebhookAccessor{
|
||||
WebhookAccessor: webhook.NewValidatingWebhookAccessor(fmt.Sprintf("webhook-%d", i), fmt.Sprintf("webhook-cfg-%d", i), testcase.webhook),
|
||||
matchResult: testcase.matchResult,
|
||||
throwError: testcase.matchError,
|
||||
}
|
||||
|
||||
invocation, err := a.ShouldCallHook(context.TODO(), fakeWebhook, testcase.attrs, interfaces, &fakeVersionedAttributeAccessor{})
|
||||
if err != nil {
|
||||
if len(testcase.expectErr) == 0 {
|
||||
t.Fatal(err)
|
||||
@ -353,7 +493,7 @@ func (f fakeNamespaceLister) Get(name string) (*corev1.Namespace, error) {
|
||||
if ok {
|
||||
return ns, nil
|
||||
}
|
||||
return nil, errors.NewNotFound(corev1.Resource("namespaces"), name)
|
||||
return nil, k8serrors.NewNotFound(corev1.Resource("namespaces"), name)
|
||||
}
|
||||
|
||||
func BenchmarkShouldCallHookWithComplexSelector(b *testing.B) {
|
||||
@ -415,13 +555,16 @@ func BenchmarkShouldCallHookWithComplexSelector(b *testing.B) {
|
||||
},
|
||||
}
|
||||
|
||||
wbAccessor := webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb)
|
||||
wbAccessor := &fakeWebhookAccessor{
|
||||
WebhookAccessor: webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb),
|
||||
matchResult: true,
|
||||
}
|
||||
attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil)
|
||||
interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper}
|
||||
a := &Webhook{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.ShouldCallHook(wbAccessor, attrs, interfaces)
|
||||
a.ShouldCallHook(context.TODO(), wbAccessor, attrs, interfaces, nil)
|
||||
}
|
||||
}
|
||||
|
||||
@ -483,13 +626,16 @@ func BenchmarkShouldCallHookWithComplexRule(b *testing.B) {
|
||||
wb.Rules = append(wb.Rules, rule)
|
||||
}
|
||||
|
||||
wbAccessor := webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb)
|
||||
wbAccessor := &fakeWebhookAccessor{
|
||||
WebhookAccessor: webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb),
|
||||
matchResult: true,
|
||||
}
|
||||
attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil)
|
||||
interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper}
|
||||
a := &Webhook{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.ShouldCallHook(wbAccessor, attrs, interfaces)
|
||||
a.ShouldCallHook(context.TODO(), wbAccessor, attrs, interfaces, &fakeVersionedAttributeAccessor{})
|
||||
}
|
||||
}
|
||||
|
||||
@ -556,12 +702,15 @@ func BenchmarkShouldCallHookWithComplexSelectorAndRule(b *testing.B) {
|
||||
wb.Rules = append(wb.Rules, rule)
|
||||
}
|
||||
|
||||
wbAccessor := webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb)
|
||||
wbAccessor := &fakeWebhookAccessor{
|
||||
WebhookAccessor: webhook.NewValidatingWebhookAccessor("webhook", "webhook-cfg", wb),
|
||||
matchResult: true,
|
||||
}
|
||||
attrs := admission.NewAttributesRecord(nil, nil, gvk("autoscaling", "v1", "Scale"), "ns", "name", gvr("apps", "v1", "deployments"), "scale", admission.Create, &metav1.CreateOptions{}, false, nil)
|
||||
interfaces := &admission.RuntimeObjectInterfaces{EquivalentResourceMapper: mapper}
|
||||
a := &Webhook{namespaceMatcher: &namespace.Matcher{NamespaceLister: namespaceLister}, objectMatcher: &object.Matcher{}}
|
||||
|
||||
for i := 0; i < b.N; i++ {
|
||||
a.ShouldCallHook(wbAccessor, attrs, interfaces)
|
||||
a.ShouldCallHook(context.TODO(), wbAccessor, attrs, interfaces, nil)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package matchconditions
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
)
|
||||
|
||||
type MatchResult struct {
|
||||
Matches bool
|
||||
Error error
|
||||
FailedConditionName string
|
||||
}
|
||||
|
||||
// Matcher contains logic for converting Evaluations to bool of matches or does not match
|
||||
type Matcher interface {
|
||||
// Match is used to take cel evaluations and convert into decisions
|
||||
Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object) MatchResult
|
||||
}
|
@ -0,0 +1,139 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package matchconditions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"github.com/google/cel-go/cel"
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilerrors "k8s.io/apimachinery/pkg/util/errors"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
admissionmetrics "k8s.io/apiserver/pkg/admission/metrics"
|
||||
celplugin "k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
celconfig "k8s.io/apiserver/pkg/apis/cel"
|
||||
"k8s.io/apiserver/pkg/authorization/authorizer"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
var _ celplugin.ExpressionAccessor = &MatchCondition{}
|
||||
|
||||
// MatchCondition contains the inputs needed to compile, evaluate and match a cel expression
|
||||
type MatchCondition v1.MatchCondition
|
||||
|
||||
func (v *MatchCondition) GetExpression() string {
|
||||
return v.Expression
|
||||
}
|
||||
|
||||
func (v *MatchCondition) ReturnTypes() []*cel.Type {
|
||||
return []*cel.Type{cel.BoolType}
|
||||
}
|
||||
|
||||
var _ Matcher = &matcher{}
|
||||
|
||||
// matcher evaluates compiled cel expressions and determines if they match the given request or not
|
||||
type matcher struct {
|
||||
filter celplugin.Filter
|
||||
authorizer authorizer.Authorizer
|
||||
failPolicy v1.FailurePolicyType
|
||||
matcherType string
|
||||
objectName string
|
||||
}
|
||||
|
||||
func NewMatcher(filter celplugin.Filter, authorizer authorizer.Authorizer, failPolicy *v1.FailurePolicyType, matcherType, objectName string) Matcher {
|
||||
var f v1.FailurePolicyType
|
||||
if failPolicy == nil {
|
||||
f = v1.Fail
|
||||
} else {
|
||||
f = *failPolicy
|
||||
}
|
||||
return &matcher{
|
||||
filter: filter,
|
||||
authorizer: authorizer,
|
||||
failPolicy: f,
|
||||
matcherType: matcherType,
|
||||
objectName: objectName,
|
||||
}
|
||||
}
|
||||
|
||||
func (m *matcher) Match(ctx context.Context, versionedAttr *admission.VersionedAttributes, versionedParams runtime.Object) MatchResult {
|
||||
evalResults, _, err := m.filter.ForInput(ctx, versionedAttr, celplugin.CreateAdmissionRequest(versionedAttr.Attributes), celplugin.OptionalVariableBindings{
|
||||
VersionedParams: versionedParams,
|
||||
Authorizer: m.authorizer,
|
||||
}, celconfig.RuntimeCELCostBudgetMatchConditions)
|
||||
|
||||
if err != nil {
|
||||
// filter returning error is unexpected and not an evaluation error so not incrementing metric here
|
||||
if m.failPolicy == v1.Fail {
|
||||
return MatchResult{
|
||||
Error: err,
|
||||
}
|
||||
} else if m.failPolicy == v1.Ignore {
|
||||
return MatchResult{
|
||||
Matches: false,
|
||||
}
|
||||
}
|
||||
//TODO: add default so that if in future we add different failure types it doesn't fall through
|
||||
}
|
||||
|
||||
errorList := []error{}
|
||||
for _, evalResult := range evalResults {
|
||||
matchCondition, ok := evalResult.ExpressionAccessor.(*MatchCondition)
|
||||
if !ok {
|
||||
// This shouldnt happen, but if it does treat same as eval error
|
||||
klog.Error("Invalid type conversion to MatchCondition")
|
||||
errorList = append(errorList, errors.New(fmt.Sprintf("internal error converting ExpressionAccessor to MatchCondition")))
|
||||
continue
|
||||
}
|
||||
if evalResult.Error != nil {
|
||||
errorList = append(errorList, evalResult.Error)
|
||||
//TODO: what's the best way to handle this metric since its reused by VAP for match conditions
|
||||
admissionmetrics.Metrics.ObserveMatchConditionEvalError(ctx, m.objectName, m.matcherType)
|
||||
}
|
||||
if evalResult.EvalResult == celtypes.False {
|
||||
// If any condition false, skip calling webhook always
|
||||
return MatchResult{
|
||||
Matches: false,
|
||||
FailedConditionName: matchCondition.Name,
|
||||
}
|
||||
}
|
||||
}
|
||||
if len(errorList) > 0 {
|
||||
// If mix of true and eval errors then resort to fail policy
|
||||
if m.failPolicy == v1.Fail {
|
||||
// mix of true and errors with fail policy fail should fail request without calling webhook
|
||||
err = utilerrors.NewAggregate(errorList)
|
||||
return MatchResult{
|
||||
Error: err,
|
||||
}
|
||||
} else if m.failPolicy == v1.Ignore {
|
||||
// if fail policy ignore then skip call to webhook
|
||||
return MatchResult{
|
||||
Matches: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
// if no results eval to false, return matches true with list of any errors encountered
|
||||
return MatchResult{
|
||||
Matches: true,
|
||||
}
|
||||
}
|
@ -0,0 +1,360 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package matchconditions
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
v1 "k8s.io/api/admissionregistration/v1"
|
||||
|
||||
celtypes "github.com/google/cel-go/common/types"
|
||||
"github.com/stretchr/testify/require"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apiserver/pkg/admission"
|
||||
"k8s.io/apiserver/pkg/admission/plugin/cel"
|
||||
)
|
||||
|
||||
var _ cel.Filter = &fakeCelFilter{}
|
||||
|
||||
type fakeCelFilter struct {
|
||||
evaluations []cel.EvaluationResult
|
||||
throwError bool
|
||||
}
|
||||
|
||||
func (f *fakeCelFilter) ForInput(context.Context, *admission.VersionedAttributes, *admissionv1.AdmissionRequest, cel.OptionalVariableBindings, int64) ([]cel.EvaluationResult, int64, error) {
|
||||
if f.throwError {
|
||||
return nil, 0, errors.New("test error")
|
||||
}
|
||||
return f.evaluations, 0, nil
|
||||
}
|
||||
|
||||
func (f *fakeCelFilter) CompilationErrors() []error {
|
||||
return []error{}
|
||||
}
|
||||
|
||||
func TestMatch(t *testing.T) {
|
||||
fakeAttr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "default", "foo", schema.GroupVersionResource{}, "", admission.Create, nil, false, nil)
|
||||
fakeVersionedAttr, _ := admission.NewVersionedAttributes(fakeAttr, schema.GroupVersionKind{}, nil)
|
||||
fail := v1.Fail
|
||||
ignore := v1.Ignore
|
||||
|
||||
cases := []struct {
|
||||
name string
|
||||
evaluations []cel.EvaluationResult
|
||||
throwError bool
|
||||
shouldMatch bool
|
||||
returnedName string
|
||||
failPolicy *v1.FailurePolicyType
|
||||
expectError string
|
||||
}{
|
||||
{
|
||||
name: "test single matches",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "test multiple match",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "test empty evals",
|
||||
evaluations: []cel.EvaluationResult{},
|
||||
shouldMatch: true,
|
||||
},
|
||||
{
|
||||
name: "test single no match",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldMatch: false,
|
||||
returnedName: "test1",
|
||||
},
|
||||
{
|
||||
name: "test multiple no match",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldMatch: false,
|
||||
returnedName: "test1",
|
||||
},
|
||||
{
|
||||
name: "test mixed with no match first",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldMatch: false,
|
||||
returnedName: "test1",
|
||||
},
|
||||
{
|
||||
name: "test mixed with no match last",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test2",
|
||||
},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldMatch: false,
|
||||
returnedName: "test1",
|
||||
},
|
||||
{
|
||||
name: "test mixed with no match middle",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test2",
|
||||
},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test1",
|
||||
},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{
|
||||
Name: "test2",
|
||||
},
|
||||
},
|
||||
},
|
||||
shouldMatch: false,
|
||||
returnedName: "test1",
|
||||
},
|
||||
{
|
||||
name: "test error, no fail policy",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
shouldMatch: true,
|
||||
throwError: true,
|
||||
expectError: "test error",
|
||||
},
|
||||
{
|
||||
name: "test error, fail policy fail",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
failPolicy: &fail,
|
||||
shouldMatch: true,
|
||||
throwError: true,
|
||||
expectError: "test error",
|
||||
},
|
||||
{
|
||||
name: "test error, fail policy ignore",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
failPolicy: &ignore,
|
||||
shouldMatch: false,
|
||||
throwError: true,
|
||||
},
|
||||
{
|
||||
name: "test mix of true, errors and false",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
Error: errors.New("test error"),
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.False,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
Error: errors.New("test error"),
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
shouldMatch: false,
|
||||
throwError: false,
|
||||
},
|
||||
{
|
||||
name: "test mix of true, errors and fail policy not set",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
Error: errors.New("test error"),
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
Error: errors.New("test error"),
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
shouldMatch: false,
|
||||
throwError: false,
|
||||
expectError: "test error",
|
||||
},
|
||||
{
|
||||
name: "test mix of true, errors and fail policy fail",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
Error: errors.New("test error"),
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
Error: errors.New("test error"),
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
failPolicy: &fail,
|
||||
shouldMatch: false,
|
||||
throwError: false,
|
||||
expectError: "test error",
|
||||
},
|
||||
{
|
||||
name: "test mix of true, errors and fail policy ignore",
|
||||
evaluations: []cel.EvaluationResult{
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
Error: errors.New("test error"),
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
EvalResult: celtypes.True,
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
{
|
||||
Error: errors.New("test error"),
|
||||
ExpressionAccessor: &MatchCondition{},
|
||||
},
|
||||
},
|
||||
failPolicy: &ignore,
|
||||
shouldMatch: false,
|
||||
throwError: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
m := NewMatcher(&fakeCelFilter{
|
||||
evaluations: tc.evaluations,
|
||||
throwError: tc.throwError,
|
||||
}, nil, tc.failPolicy, "test", "testhook")
|
||||
ctx := context.TODO()
|
||||
matchResult := m.Match(ctx, fakeVersionedAttr, nil)
|
||||
|
||||
if matchResult.Error != nil {
|
||||
if len(tc.expectError) == 0 {
|
||||
t.Fatal(matchResult.Error)
|
||||
}
|
||||
if !strings.Contains(matchResult.Error.Error(), tc.expectError) {
|
||||
t.Fatalf("expected error containing %q, got %s", tc.expectError, matchResult.Error.Error())
|
||||
}
|
||||
return
|
||||
} else if len(tc.expectError) > 0 {
|
||||
t.Fatal("expected error but did not get one")
|
||||
}
|
||||
if len(tc.expectError) > 0 && matchResult.Error == nil {
|
||||
t.Errorf("expected error thrown when filter errors")
|
||||
}
|
||||
|
||||
require.Equal(t, tc.shouldMatch, matchResult.Matches)
|
||||
require.Equal(t, tc.returnedName, matchResult.FailedConditionName)
|
||||
})
|
||||
}
|
||||
}
|
@ -26,14 +26,13 @@ import (
|
||||
jsonpatch "github.com/evanphx/json-patch"
|
||||
"go.opentelemetry.io/otel/attribute"
|
||||
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
apiequality "k8s.io/apimachinery/pkg/api/equality"
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer/json"
|
||||
utiljson "k8s.io/apimachinery/pkg/util/json"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
@ -48,6 +47,7 @@ import (
|
||||
webhookutil "k8s.io/apiserver/pkg/util/webhook"
|
||||
"k8s.io/apiserver/pkg/warning"
|
||||
"k8s.io/component-base/tracing"
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
@ -75,6 +75,30 @@ func newMutatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generi
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.VersionedAttributeAccessor = &versionedAttributeAccessor{}
|
||||
|
||||
type versionedAttributeAccessor struct {
|
||||
versionedAttr *admission.VersionedAttributes
|
||||
attr admission.Attributes
|
||||
objectInterfaces admission.ObjectInterfaces
|
||||
}
|
||||
|
||||
func (v *versionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error) {
|
||||
if v.versionedAttr == nil {
|
||||
// First call, create versioned attributes
|
||||
var err error
|
||||
if v.versionedAttr, err = admission.NewVersionedAttributes(v.attr, gvk, v.objectInterfaces); err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
} else {
|
||||
// Subsequent call, convert existing versioned attributes to the requested version
|
||||
if err := admission.ConvertVersionedAttributes(v.versionedAttr, gvk, v.objectInterfaces); err != nil {
|
||||
return nil, apierrors.NewInternalError(err)
|
||||
}
|
||||
}
|
||||
return v.versionedAttr, nil
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &mutatingDispatcher{}
|
||||
|
||||
func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
@ -95,19 +119,24 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
|
||||
defer func() {
|
||||
webhookReinvokeCtx.SetLastWebhookInvocationOutput(attr.GetObject())
|
||||
}()
|
||||
var versionedAttr *admission.VersionedAttributes
|
||||
v := &versionedAttributeAccessor{
|
||||
attr: attr,
|
||||
objectInterfaces: o,
|
||||
}
|
||||
for i, hook := range hooks {
|
||||
attrForCheck := attr
|
||||
if versionedAttr != nil {
|
||||
attrForCheck = versionedAttr
|
||||
if v.versionedAttr != nil {
|
||||
attrForCheck = v.versionedAttr
|
||||
}
|
||||
invocation, statusErr := a.plugin.ShouldCallHook(hook, attrForCheck, o)
|
||||
|
||||
invocation, statusErr := a.plugin.ShouldCallHook(ctx, hook, attrForCheck, o, v)
|
||||
if statusErr != nil {
|
||||
return statusErr
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
hook, ok := invocation.Webhook.GetMutatingWebhook()
|
||||
if !ok {
|
||||
return fmt.Errorf("mutating webhook dispatch requires v1.MutatingWebhook, but got %T", hook)
|
||||
@ -121,17 +150,9 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
|
||||
continue
|
||||
}
|
||||
|
||||
if versionedAttr == nil {
|
||||
// First webhook, create versioned attributes
|
||||
var err error
|
||||
if versionedAttr, err = admission.NewVersionedAttributes(attr, invocation.Kind, o); err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
} else {
|
||||
// Subsequent webhook, convert existing versioned attributes to this webhook's version
|
||||
if err := admission.ConvertVersionedAttributes(versionedAttr, invocation.Kind, o); err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttr, err := v.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
|
||||
t := time.Now()
|
||||
@ -203,8 +224,8 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib
|
||||
}
|
||||
|
||||
// convert versionedAttr.VersionedObject to the internal version in the underlying admission.Attributes
|
||||
if versionedAttr != nil && versionedAttr.VersionedObject != nil && versionedAttr.Dirty {
|
||||
return o.GetObjectConvertor().Convert(versionedAttr.VersionedObject, versionedAttr.Attributes.GetObject(), nil)
|
||||
if v.versionedAttr != nil && v.versionedAttr.VersionedObject != nil && v.versionedAttr.Dirty {
|
||||
return o.GetObjectConvertor().Convert(v.versionedAttr.VersionedObject, v.versionedAttr.Attributes.GetObject(), nil)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -62,30 +62,51 @@ func newValidatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) gene
|
||||
}
|
||||
}
|
||||
|
||||
var _ generic.VersionedAttributeAccessor = &versionedAttributeAccessor{}
|
||||
|
||||
type versionedAttributeAccessor struct {
|
||||
versionedAttrs map[schema.GroupVersionKind]*admission.VersionedAttributes
|
||||
attr admission.Attributes
|
||||
objectInterfaces admission.ObjectInterfaces
|
||||
}
|
||||
|
||||
func (v *versionedAttributeAccessor) VersionedAttribute(gvk schema.GroupVersionKind) (*admission.VersionedAttributes, error) {
|
||||
if val, ok := v.versionedAttrs[gvk]; ok {
|
||||
return val, nil
|
||||
}
|
||||
versionedAttr, err := admission.NewVersionedAttributes(v.attr, gvk, v.objectInterfaces)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
v.versionedAttrs[gvk] = versionedAttr
|
||||
return versionedAttr, nil
|
||||
}
|
||||
|
||||
var _ generic.Dispatcher = &validatingDispatcher{}
|
||||
|
||||
func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error {
|
||||
var relevantHooks []*generic.WebhookInvocation
|
||||
// Construct all the versions we need to call our webhooks
|
||||
versionedAttrs := map[schema.GroupVersionKind]*admission.VersionedAttributes{}
|
||||
versionedAttrAccessor := &versionedAttributeAccessor{
|
||||
versionedAttrs: map[schema.GroupVersionKind]*admission.VersionedAttributes{},
|
||||
attr: attr,
|
||||
objectInterfaces: o,
|
||||
}
|
||||
for _, hook := range hooks {
|
||||
invocation, statusError := d.plugin.ShouldCallHook(hook, attr, o)
|
||||
invocation, statusError := d.plugin.ShouldCallHook(ctx, hook, attr, o, versionedAttrAccessor)
|
||||
if statusError != nil {
|
||||
return statusError
|
||||
}
|
||||
if invocation == nil {
|
||||
continue
|
||||
}
|
||||
|
||||
relevantHooks = append(relevantHooks, invocation)
|
||||
// If we already have this version, continue
|
||||
if _, ok := versionedAttrs[invocation.Kind]; ok {
|
||||
continue
|
||||
}
|
||||
versionedAttr, err := admission.NewVersionedAttributes(attr, invocation.Kind, o)
|
||||
// VersionedAttr result will be cached and reused later during parallel webhook calls
|
||||
_, err := versionedAttrAccessor.VersionedAttribute(invocation.Kind)
|
||||
if err != nil {
|
||||
return apierrors.NewInternalError(err)
|
||||
}
|
||||
versionedAttrs[invocation.Kind] = versionedAttr
|
||||
}
|
||||
|
||||
if len(relevantHooks) == 0 {
|
||||
@ -108,7 +129,7 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attr
|
||||
go func(invocation *generic.WebhookInvocation, idx int) {
|
||||
ignoreClientCallFailures := false
|
||||
hookName := "unknown"
|
||||
versionedAttr := versionedAttrs[invocation.Kind]
|
||||
versionedAttr := versionedAttrAccessor.versionedAttrs[invocation.Kind]
|
||||
// The ordering of these two defers is critical. The wg.Done will release the parent go func to close the errCh
|
||||
// that is used by the second defer to report errors. The recovery and error reporting must be done first.
|
||||
defer wg.Done()
|
||||
|
@ -25,6 +25,11 @@ const (
|
||||
// current RuntimeCELCostBudget gives roughly 1 seconds for the validation
|
||||
RuntimeCELCostBudget = 10000000
|
||||
|
||||
// RuntimeCELCostBudgetMatchConditions is the overall cost budget for runtime CEL validation cost on matchConditions per object with matchConditions
|
||||
// this is per webhook for validatingwebhookconfigurations and mutatingwebhookconfigurations or per ValidatingAdmissionPolicyBinding
|
||||
// current RuntimeCELCostBudgetMatchConditions gives roughly 1/4 seconds for the validation
|
||||
RuntimeCELCostBudgetMatchConditions = 2500000
|
||||
|
||||
// CheckFrequency configures the number of iterations within a comprehension to evaluate
|
||||
// before checking whether the function evaluation has been interrupted
|
||||
CheckFrequency = 100
|
||||
|
@ -35,6 +35,13 @@ const (
|
||||
// of code conflicts because changes are more likely to be scattered
|
||||
// across the file.
|
||||
|
||||
// owner: @ivelichkovich, @tallclair
|
||||
// alpha: v1.27
|
||||
// kep: https://kep.k8s.io/3716
|
||||
//
|
||||
// Enables usage of MatchConditions fields to use CEL expressions for matching on admission webhooks
|
||||
AdmissionWebhookMatchConditions featuregate.Feature = "AdmissionWebhookMatchConditions"
|
||||
|
||||
// owner: @jefftree @alexzielenski
|
||||
// alpha: v1.26
|
||||
//
|
||||
@ -223,8 +230,11 @@ func init() {
|
||||
// To add a new feature, define a key for it above and add it here. The features will be
|
||||
// available throughout Kubernetes binaries.
|
||||
var defaultKubernetesFeatureGates = map[featuregate.Feature]featuregate.FeatureSpec{
|
||||
|
||||
AggregatedDiscoveryEndpoint: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
||||
AdmissionWebhookMatchConditions: {Default: false, PreRelease: featuregate.Alpha},
|
||||
|
||||
APIListChunking: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
||||
APIPriorityAndFairness: {Default: true, PreRelease: featuregate.Beta},
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1
|
||||
|
||||
// MatchConditionApplyConfiguration represents an declarative configuration of the MatchCondition type for use
|
||||
// with apply.
|
||||
type MatchConditionApplyConfiguration struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Expression *string `json:"expression,omitempty"`
|
||||
}
|
||||
|
||||
// MatchConditionApplyConfiguration constructs an declarative configuration of the MatchCondition type for use with
|
||||
// apply.
|
||||
func MatchCondition() *MatchConditionApplyConfiguration {
|
||||
return &MatchConditionApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithName sets the Name field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Name field is set to the value of the last call.
|
||||
func (b *MatchConditionApplyConfiguration) WithName(value string) *MatchConditionApplyConfiguration {
|
||||
b.Name = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithExpression sets the Expression field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Expression field is set to the value of the last call.
|
||||
func (b *MatchConditionApplyConfiguration) WithExpression(value string) *MatchConditionApplyConfiguration {
|
||||
b.Expression = &value
|
||||
return b
|
||||
}
|
@ -37,6 +37,7 @@ type MutatingWebhookApplyConfiguration struct {
|
||||
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
|
||||
AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty"`
|
||||
ReinvocationPolicy *admissionregistrationv1.ReinvocationPolicyType `json:"reinvocationPolicy,omitempty"`
|
||||
MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"`
|
||||
}
|
||||
|
||||
// MutatingWebhookApplyConfiguration constructs an declarative configuration of the MutatingWebhook type for use with
|
||||
@ -139,3 +140,16 @@ func (b *MutatingWebhookApplyConfiguration) WithReinvocationPolicy(value admissi
|
||||
b.ReinvocationPolicy = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMatchConditions adds the given value to the MatchConditions field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the MatchConditions field.
|
||||
func (b *MutatingWebhookApplyConfiguration) WithMatchConditions(values ...*MatchConditionApplyConfiguration) *MutatingWebhookApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithMatchConditions")
|
||||
}
|
||||
b.MatchConditions = append(b.MatchConditions, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
@ -36,6 +36,7 @@ type ValidatingWebhookApplyConfiguration struct {
|
||||
SideEffects *admissionregistrationv1.SideEffectClass `json:"sideEffects,omitempty"`
|
||||
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
|
||||
AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty"`
|
||||
MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"`
|
||||
}
|
||||
|
||||
// ValidatingWebhookApplyConfiguration constructs an declarative configuration of the ValidatingWebhook type for use with
|
||||
@ -130,3 +131,16 @@ func (b *ValidatingWebhookApplyConfiguration) WithAdmissionReviewVersions(values
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMatchConditions adds the given value to the MatchConditions field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the MatchConditions field.
|
||||
func (b *ValidatingWebhookApplyConfiguration) WithMatchConditions(values ...*MatchConditionApplyConfiguration) *ValidatingWebhookApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithMatchConditions")
|
||||
}
|
||||
b.MatchConditions = append(b.MatchConditions, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
@ -0,0 +1,48 @@
|
||||
/*
|
||||
Copyright The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
// Code generated by applyconfiguration-gen. DO NOT EDIT.
|
||||
|
||||
package v1beta1
|
||||
|
||||
// MatchConditionApplyConfiguration represents an declarative configuration of the MatchCondition type for use
|
||||
// with apply.
|
||||
type MatchConditionApplyConfiguration struct {
|
||||
Name *string `json:"name,omitempty"`
|
||||
Expression *string `json:"expression,omitempty"`
|
||||
}
|
||||
|
||||
// MatchConditionApplyConfiguration constructs an declarative configuration of the MatchCondition type for use with
|
||||
// apply.
|
||||
func MatchCondition() *MatchConditionApplyConfiguration {
|
||||
return &MatchConditionApplyConfiguration{}
|
||||
}
|
||||
|
||||
// WithName sets the Name field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Name field is set to the value of the last call.
|
||||
func (b *MatchConditionApplyConfiguration) WithName(value string) *MatchConditionApplyConfiguration {
|
||||
b.Name = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithExpression sets the Expression field in the declarative configuration to the given value
|
||||
// and returns the receiver, so that objects can be built by chaining "With" function invocations.
|
||||
// If called multiple times, the Expression field is set to the value of the last call.
|
||||
func (b *MatchConditionApplyConfiguration) WithExpression(value string) *MatchConditionApplyConfiguration {
|
||||
b.Expression = &value
|
||||
return b
|
||||
}
|
@ -38,6 +38,7 @@ type MutatingWebhookApplyConfiguration struct {
|
||||
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
|
||||
AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty"`
|
||||
ReinvocationPolicy *admissionregistrationv1beta1.ReinvocationPolicyType `json:"reinvocationPolicy,omitempty"`
|
||||
MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"`
|
||||
}
|
||||
|
||||
// MutatingWebhookApplyConfiguration constructs an declarative configuration of the MutatingWebhook type for use with
|
||||
@ -140,3 +141,16 @@ func (b *MutatingWebhookApplyConfiguration) WithReinvocationPolicy(value admissi
|
||||
b.ReinvocationPolicy = &value
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMatchConditions adds the given value to the MatchConditions field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the MatchConditions field.
|
||||
func (b *MutatingWebhookApplyConfiguration) WithMatchConditions(values ...*MatchConditionApplyConfiguration) *MutatingWebhookApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithMatchConditions")
|
||||
}
|
||||
b.MatchConditions = append(b.MatchConditions, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
@ -37,6 +37,7 @@ type ValidatingWebhookApplyConfiguration struct {
|
||||
SideEffects *admissionregistrationv1beta1.SideEffectClass `json:"sideEffects,omitempty"`
|
||||
TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty"`
|
||||
AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty"`
|
||||
MatchConditions []MatchConditionApplyConfiguration `json:"matchConditions,omitempty"`
|
||||
}
|
||||
|
||||
// ValidatingWebhookApplyConfiguration constructs an declarative configuration of the ValidatingWebhook type for use with
|
||||
@ -131,3 +132,16 @@ func (b *ValidatingWebhookApplyConfiguration) WithAdmissionReviewVersions(values
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// WithMatchConditions adds the given value to the MatchConditions field in the declarative configuration
|
||||
// and returns the receiver, so that objects can be build by chaining "With" function invocations.
|
||||
// If called multiple times, values provided by each call will be appended to the MatchConditions field.
|
||||
func (b *ValidatingWebhookApplyConfiguration) WithMatchConditions(values ...*MatchConditionApplyConfiguration) *ValidatingWebhookApplyConfiguration {
|
||||
for i := range values {
|
||||
if values[i] == nil {
|
||||
panic("nil value passed to WithMatchConditions")
|
||||
}
|
||||
b.MatchConditions = append(b.MatchConditions, *values[i])
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
@ -39,6 +39,17 @@ func Parser() *typed.Parser {
|
||||
var parserOnce sync.Once
|
||||
var parser *typed.Parser
|
||||
var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: io.k8s.api.admissionregistration.v1.MatchCondition
|
||||
map:
|
||||
fields:
|
||||
- name: expression
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: name
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: io.k8s.api.admissionregistration.v1.MutatingWebhook
|
||||
map:
|
||||
fields:
|
||||
@ -55,6 +66,14 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: failurePolicy
|
||||
type:
|
||||
scalar: string
|
||||
- name: matchConditions
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.admissionregistration.v1.MatchCondition
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- name
|
||||
- name: matchPolicy
|
||||
type:
|
||||
scalar: string
|
||||
@ -167,6 +186,14 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: failurePolicy
|
||||
type:
|
||||
scalar: string
|
||||
- name: matchConditions
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.admissionregistration.v1.MatchCondition
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- name
|
||||
- name: matchPolicy
|
||||
type:
|
||||
scalar: string
|
||||
@ -451,6 +478,17 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: reason
|
||||
type:
|
||||
scalar: string
|
||||
- name: io.k8s.api.admissionregistration.v1beta1.MatchCondition
|
||||
map:
|
||||
fields:
|
||||
- name: expression
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: name
|
||||
type:
|
||||
scalar: string
|
||||
default: ""
|
||||
- name: io.k8s.api.admissionregistration.v1beta1.MutatingWebhook
|
||||
map:
|
||||
fields:
|
||||
@ -467,6 +505,14 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: failurePolicy
|
||||
type:
|
||||
scalar: string
|
||||
- name: matchConditions
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.admissionregistration.v1beta1.MatchCondition
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- name
|
||||
- name: matchPolicy
|
||||
type:
|
||||
scalar: string
|
||||
@ -549,6 +595,14 @@ var schemaYAML = typed.YAMLObject(`types:
|
||||
- name: failurePolicy
|
||||
type:
|
||||
scalar: string
|
||||
- name: matchConditions
|
||||
type:
|
||||
list:
|
||||
elementType:
|
||||
namedType: io.k8s.api.admissionregistration.v1beta1.MatchCondition
|
||||
elementRelationship: associative
|
||||
keys:
|
||||
- name
|
||||
- name: matchPolicy
|
||||
type:
|
||||
scalar: string
|
||||
|
@ -121,6 +121,8 @@ import (
|
||||
func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
switch kind {
|
||||
// Group=admissionregistration.k8s.io, Version=v1
|
||||
case v1.SchemeGroupVersion.WithKind("MatchCondition"):
|
||||
return &admissionregistrationv1.MatchConditionApplyConfiguration{}
|
||||
case v1.SchemeGroupVersion.WithKind("MutatingWebhook"):
|
||||
return &admissionregistrationv1.MutatingWebhookApplyConfiguration{}
|
||||
case v1.SchemeGroupVersion.WithKind("MutatingWebhookConfiguration"):
|
||||
@ -167,6 +169,8 @@ func ForKind(kind schema.GroupVersionKind) interface{} {
|
||||
return &admissionregistrationv1alpha1.ValidationApplyConfiguration{}
|
||||
|
||||
// Group=admissionregistration.k8s.io, Version=v1beta1
|
||||
case v1beta1.SchemeGroupVersion.WithKind("MatchCondition"):
|
||||
return &admissionregistrationv1beta1.MatchConditionApplyConfiguration{}
|
||||
case v1beta1.SchemeGroupVersion.WithKind("MutatingWebhook"):
|
||||
return &admissionregistrationv1beta1.MutatingWebhookApplyConfiguration{}
|
||||
case v1beta1.SchemeGroupVersion.WithKind("MutatingWebhookConfiguration"):
|
||||
|
@ -0,0 +1,712 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admissionwebhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
type admissionRecorder struct {
|
||||
mu sync.Mutex
|
||||
upCh chan struct{}
|
||||
upOnce sync.Once
|
||||
requests []*admissionv1.AdmissionRequest
|
||||
}
|
||||
|
||||
func (r *admissionRecorder) Record(req *admissionv1.AdmissionRequest) {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.requests = append(r.requests, req)
|
||||
}
|
||||
|
||||
func (r *admissionRecorder) MarkerReceived() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.upOnce.Do(func() {
|
||||
close(r.upCh)
|
||||
})
|
||||
}
|
||||
|
||||
func (r *admissionRecorder) Reset() chan struct{} {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.requests = []*admissionv1.AdmissionRequest{}
|
||||
r.upCh = make(chan struct{})
|
||||
r.upOnce = sync.Once{}
|
||||
return r.upCh
|
||||
}
|
||||
|
||||
func newMatchConditionHandler(recorder *admissionRecorder) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
}
|
||||
review := admissionv1.AdmissionReview{}
|
||||
if err := json.Unmarshal(data, &review); err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
}
|
||||
|
||||
review.Response = &admissionv1.AdmissionResponse{
|
||||
Allowed: true,
|
||||
UID: review.Request.UID,
|
||||
Result: &metav1.Status{Message: "admitted"},
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(review); err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/marker":
|
||||
recorder.MarkerReceived()
|
||||
return
|
||||
}
|
||||
|
||||
recorder.Record(review.Request)
|
||||
})
|
||||
}
|
||||
|
||||
// Test_MatchConditions tests ValidatingWebhookConfigurations and MutatingWebhookConfigurations that validates different cases of matchCondition fields
|
||||
func Test_MatchConditions(t *testing.T) {
|
||||
fail := admissionregistrationv1.Fail
|
||||
ignore := admissionregistrationv1.Ignore
|
||||
|
||||
testcases := []struct {
|
||||
name string
|
||||
matchConditions []admissionregistrationv1.MatchCondition
|
||||
pods []*corev1.Pod
|
||||
matchedPods []*corev1.Pod
|
||||
expectErrorWH bool
|
||||
expectErrorPod bool
|
||||
failPolicy *admissionregistrationv1.FailurePolicyType
|
||||
}{
|
||||
{
|
||||
name: "pods in namespace kube-system is ignored",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "pods-in-kube-system-exempt.kubernetes.io",
|
||||
Expression: "object.metadata.namespace != 'kube-system'",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test1", "kube-system"),
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
matchedPods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
expectErrorWH: false,
|
||||
},
|
||||
{
|
||||
name: "matchConditions are ANDed together",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "pods-in-kube-system-exempt.kubernetes.io",
|
||||
Expression: "object.metadata.namespace != 'kube-system'",
|
||||
},
|
||||
{
|
||||
Name: "pods-with-name-test1.kubernetes.io",
|
||||
Expression: "object.metadata.name == 'test1'",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test1", "kube-system"),
|
||||
matchConditionsTestPod("test1", "default"),
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
matchedPods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test1", "default"),
|
||||
},
|
||||
expectErrorWH: false,
|
||||
},
|
||||
{
|
||||
name: "mix of true, error and false should not match and not call webhook",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
Expression: "object.nonExistentProperty == 'someval'",
|
||||
},
|
||||
{
|
||||
Name: "test2",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "test3",
|
||||
Expression: "false",
|
||||
},
|
||||
{
|
||||
Name: "test4",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "test5",
|
||||
Expression: "object.nonExistentProperty == 'someval'",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test1", "kube-system"),
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: false,
|
||||
expectErrorPod: false,
|
||||
},
|
||||
{
|
||||
name: "mix of true and error should reject request without fail policy",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
Expression: "object.nonExistentProperty == 'someval'",
|
||||
},
|
||||
{
|
||||
Name: "test2",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "test4",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "test5",
|
||||
Expression: "object.nonExistentProperty == 'someval'",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test1", "kube-system"),
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: false,
|
||||
expectErrorPod: true,
|
||||
},
|
||||
{
|
||||
name: "mix of true and error should reject request with fail policy fail",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
Expression: "object.nonExistentProperty == 'someval'",
|
||||
},
|
||||
{
|
||||
Name: "test2",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "test4",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "test5",
|
||||
Expression: "object.nonExistentProperty == 'someval'",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test1", "kube-system"),
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
failPolicy: &fail,
|
||||
expectErrorWH: false,
|
||||
expectErrorPod: true,
|
||||
},
|
||||
{
|
||||
name: "mix of true and error should match request and call webhook with fail policy ignore",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "tes1",
|
||||
Expression: "object.nonExistentProperty == 'someval'",
|
||||
},
|
||||
{
|
||||
Name: "test2",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "test4",
|
||||
Expression: "true",
|
||||
},
|
||||
{
|
||||
Name: "test5",
|
||||
Expression: "object.nonExistentProperty == 'someval'",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test1", "kube-system"),
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
failPolicy: &ignore,
|
||||
expectErrorWH: false,
|
||||
},
|
||||
{
|
||||
name: "has access to oldObject",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "old-object-is-null.kubernetes.io",
|
||||
Expression: "oldObject == null",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
matchedPods: []*corev1.Pod{
|
||||
matchConditionsTestPod("test2", "default"),
|
||||
},
|
||||
expectErrorWH: false,
|
||||
},
|
||||
{
|
||||
name: "invalid field should error",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "old-object-is-null.kubernetes.io",
|
||||
Expression: "imnotafield == null",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: true,
|
||||
},
|
||||
{
|
||||
name: "missing expression should error",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "old-object-is-null.kubernetes.io",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: true,
|
||||
},
|
||||
{
|
||||
name: "missing name should error",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Expression: "oldObject == null",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: true,
|
||||
},
|
||||
{
|
||||
name: "empty name should error",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "",
|
||||
Expression: "oldObject == null",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: true,
|
||||
},
|
||||
{
|
||||
name: "empty expression should error",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "test-empty-expression.kubernetes.io",
|
||||
Expression: "",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: true,
|
||||
},
|
||||
{
|
||||
name: "duplicate name should error",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "test1",
|
||||
Expression: "oldObject == null",
|
||||
},
|
||||
{
|
||||
Name: "test1",
|
||||
Expression: "oldObject == null",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: true,
|
||||
},
|
||||
{
|
||||
name: "name must be qualified name",
|
||||
matchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: " test1",
|
||||
Expression: "oldObject == null",
|
||||
},
|
||||
},
|
||||
pods: []*corev1.Pod{},
|
||||
matchedPods: []*corev1.Pod{},
|
||||
expectErrorWH: true,
|
||||
},
|
||||
}
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
if !roots.AppendCertsFromPEM(localhostCert) {
|
||||
t.Fatal("Failed to append Cert from PEM")
|
||||
}
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build cert with error: %+v", err)
|
||||
}
|
||||
|
||||
recorder := &admissionRecorder{requests: []*admissionv1.AdmissionRequest{}}
|
||||
|
||||
webhookServer := httptest.NewUnstartedServer(newMatchConditionHandler(recorder))
|
||||
webhookServer.TLS = &tls.Config{
|
||||
RootCAs: roots,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
webhookServer.StartTLS()
|
||||
defer webhookServer.Close()
|
||||
|
||||
for _, testcase := range testcases {
|
||||
t.Run(testcase.name, func(t *testing.T) {
|
||||
upCh := recorder.Reset()
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AdmissionWebhookMatchConditions, true)()
|
||||
|
||||
server, err := apiservertesting.StartTestServer(t, nil, []string{
|
||||
"--disable-admission-plugins=ServiceAccount",
|
||||
}, framework.SharedEtcd())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer server.TearDownFn()
|
||||
|
||||
config := server.ClientConfig
|
||||
|
||||
client, err := clientset.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write markers to a separate namespace to avoid cross-talk
|
||||
markerNs := "marker"
|
||||
_, err = client.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: markerNs}}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a marker object to use to check for the webhook configurations to be ready.
|
||||
marker, err := client.CoreV1().Pods(markerNs).Create(context.TODO(), newMarkerPod(markerNs), metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
endpoint := webhookServer.URL
|
||||
markerEndpoint := webhookServer.URL + "/marker"
|
||||
validatingwebhook := &admissionregistrationv1.ValidatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "admission.integration.test",
|
||||
},
|
||||
Webhooks: []admissionregistrationv1.ValidatingWebhook{
|
||||
{
|
||||
Name: "admission.integration.test",
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{""},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
}},
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &endpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
// ignore pods in the marker namespace
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: corev1.LabelMetadataName,
|
||||
Operator: metav1.LabelSelectorOpNotIn,
|
||||
Values: []string{"marker"},
|
||||
},
|
||||
}},
|
||||
FailurePolicy: testcase.failPolicy,
|
||||
SideEffects: &noSideEffects,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
MatchConditions: testcase.matchConditions,
|
||||
},
|
||||
{
|
||||
Name: "admission.integration.test.marker",
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
|
||||
Rule: admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
|
||||
}},
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &markerEndpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
|
||||
corev1.LabelMetadataName: "marker",
|
||||
}},
|
||||
ObjectSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
|
||||
FailurePolicy: testcase.failPolicy,
|
||||
SideEffects: &noSideEffects,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
validatingcfg, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(context.TODO(), validatingwebhook, metav1.CreateOptions{})
|
||||
if testcase.expectErrorWH == false && err != nil {
|
||||
t.Fatal(err)
|
||||
} else if testcase.expectErrorWH == true {
|
||||
if err == nil {
|
||||
t.Fatal("expected error creating ValidatingWebhookConfigurations")
|
||||
}
|
||||
return
|
||||
}
|
||||
vhwHasBeenCleanedUp := false
|
||||
defer func() {
|
||||
if !vhwHasBeenCleanedUp {
|
||||
err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), validatingcfg.GetName(), metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
}()
|
||||
|
||||
// wait until new webhook is called the first time
|
||||
if err := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
_, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
|
||||
select {
|
||||
case <-upCh:
|
||||
return true, nil
|
||||
default:
|
||||
t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, pod := range testcase.pods {
|
||||
_, err := client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
|
||||
if testcase.expectErrorPod == false && err != nil {
|
||||
t.Fatalf("unexpected error creating test pod: %v", err)
|
||||
} else if testcase.expectErrorWH == true {
|
||||
if err == nil {
|
||||
t.Fatal("expected error creating pods")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(recorder.requests) != len(testcase.matchedPods) {
|
||||
t.Errorf("unexpected requests %v, expected %v", recorder.requests, testcase.matchedPods)
|
||||
}
|
||||
|
||||
for i, request := range recorder.requests {
|
||||
if request.Name != testcase.matchedPods[i].Name {
|
||||
t.Errorf("unexpected pod name %v, expected %v", request.Name, testcase.matchedPods[i].Name)
|
||||
}
|
||||
if request.Namespace != testcase.matchedPods[i].Namespace {
|
||||
t.Errorf("unexpected pod namespace %v, expected %v", request.Namespace, testcase.matchedPods[i].Namespace)
|
||||
}
|
||||
}
|
||||
|
||||
//Reset and rerun against mutating webhook configuration
|
||||
//TODO: private helper function for validation after creating vwh or mwh
|
||||
upCh = recorder.Reset()
|
||||
err = client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Delete(context.TODO(), validatingcfg.GetName(), metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
} else {
|
||||
vhwHasBeenCleanedUp = true
|
||||
}
|
||||
|
||||
mutatingwebhook := &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "admission.integration.test",
|
||||
},
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
{
|
||||
Name: "admission.integration.test",
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{""},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"pods"},
|
||||
},
|
||||
}},
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &endpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
// ignore pods in the marker namespace
|
||||
NamespaceSelector: &metav1.LabelSelector{
|
||||
MatchExpressions: []metav1.LabelSelectorRequirement{
|
||||
{
|
||||
Key: corev1.LabelMetadataName,
|
||||
Operator: metav1.LabelSelectorOpNotIn,
|
||||
Values: []string{"marker"},
|
||||
},
|
||||
}},
|
||||
FailurePolicy: testcase.failPolicy,
|
||||
SideEffects: &noSideEffects,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
MatchConditions: testcase.matchConditions,
|
||||
},
|
||||
{
|
||||
Name: "admission.integration.test.marker",
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
|
||||
Rule: admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
|
||||
}},
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &markerEndpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
|
||||
corev1.LabelMetadataName: "marker",
|
||||
}},
|
||||
ObjectSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
|
||||
FailurePolicy: testcase.failPolicy,
|
||||
SideEffects: &noSideEffects,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mutatingcfg, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), mutatingwebhook, metav1.CreateOptions{})
|
||||
if testcase.expectErrorWH == false && err != nil {
|
||||
t.Fatal(err)
|
||||
} else if testcase.expectErrorWH == true {
|
||||
if err == nil {
|
||||
t.Fatal("expected error creating MutatingWebhookConfiguration")
|
||||
}
|
||||
return
|
||||
}
|
||||
defer func() {
|
||||
err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(context.TODO(), mutatingcfg.GetName(), metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// wait until new webhook is called the first time
|
||||
if err := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
_, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
|
||||
select {
|
||||
case <-upCh:
|
||||
return true, nil
|
||||
default:
|
||||
t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
for _, pod := range testcase.pods {
|
||||
if !testcase.expectErrorPod {
|
||||
err := client.CoreV1().Pods(pod.Namespace).Delete(context.TODO(), pod.Name, metav1.DeleteOptions{})
|
||||
//TODO: should probably confirm deleted
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error deleting pods %v", err.Error())
|
||||
}
|
||||
}
|
||||
_, err = client.CoreV1().Pods(pod.Namespace).Create(context.TODO(), pod, metav1.CreateOptions{})
|
||||
if testcase.expectErrorPod == false && err != nil {
|
||||
t.Fatalf("unexpected error creating test pod: %v", err)
|
||||
} else if testcase.expectErrorWH == true {
|
||||
if err == nil {
|
||||
t.Fatal("expected error creating pods")
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
if len(recorder.requests) != len(testcase.matchedPods) {
|
||||
t.Errorf("unexpected requests %v, expected %v", recorder.requests, testcase.matchedPods)
|
||||
}
|
||||
|
||||
for i, request := range recorder.requests {
|
||||
if request.Name != testcase.matchedPods[i].Name {
|
||||
t.Errorf("unexpected pod name %v, expected %v", request.Name, testcase.matchedPods[i].Name)
|
||||
}
|
||||
if request.Namespace != testcase.matchedPods[i].Namespace {
|
||||
t.Errorf("unexpected pod namespace %v, expected %v", request.Namespace, testcase.matchedPods[i].Namespace)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func matchConditionsTestPod(name, ns string) *corev1.Pod {
|
||||
return &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: name,
|
||||
Namespace: ns,
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{
|
||||
{
|
||||
Name: "test",
|
||||
Image: "test",
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func newMarkerPod(namespace string) *corev1.Pod {
|
||||
return &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: "marker",
|
||||
Labels: map[string]string{
|
||||
"marker": "true",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "fake-name",
|
||||
Image: "fakeimage",
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
@ -0,0 +1,400 @@
|
||||
/*
|
||||
Copyright 2023 The Kubernetes Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package admissionwebhook
|
||||
|
||||
import (
|
||||
"context"
|
||||
"crypto/tls"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
admissionv1 "k8s.io/api/admission/v1"
|
||||
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
|
||||
corev1 "k8s.io/api/core/v1"
|
||||
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
|
||||
apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
|
||||
"k8s.io/apiextensions-apiserver/test/integration/fixtures"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/runtime/serializer"
|
||||
"k8s.io/apimachinery/pkg/types"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
genericfeatures "k8s.io/apiserver/pkg/features"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
"k8s.io/client-go/dynamic"
|
||||
clientset "k8s.io/client-go/kubernetes"
|
||||
featuregatetesting "k8s.io/component-base/featuregate/testing"
|
||||
apiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing"
|
||||
"k8s.io/kubernetes/test/integration/etcd"
|
||||
"k8s.io/kubernetes/test/integration/framework"
|
||||
)
|
||||
|
||||
var (
|
||||
runtimeSchemeGVKTest = runtime.NewScheme()
|
||||
codecFactoryGVKTest = serializer.NewCodecFactory(runtimeSchemeGVKTest)
|
||||
deserializerGVKTest = codecFactoryGVKTest.UniversalDeserializer()
|
||||
)
|
||||
|
||||
type admissionTypeChecker struct {
|
||||
mu sync.Mutex
|
||||
upCh chan struct{}
|
||||
upOnce sync.Once
|
||||
requests []*admissionv1.AdmissionRequest
|
||||
}
|
||||
|
||||
func (r *admissionTypeChecker) Reset() chan struct{} {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.upCh = make(chan struct{})
|
||||
r.upOnce = sync.Once{}
|
||||
r.requests = []*admissionv1.AdmissionRequest{}
|
||||
return r.upCh
|
||||
}
|
||||
|
||||
func (r *admissionTypeChecker) TypeCheck(req *admissionv1.AdmissionRequest, version string) *admissionv1.AdmissionResponse {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.requests = append(r.requests, req)
|
||||
raw := req.Object.Raw
|
||||
var into runtime.Object
|
||||
if _, gvk, err := deserializerGVKTest.Decode(raw, nil, into); err != nil {
|
||||
if gvk.Version != version {
|
||||
return &admissionv1.AdmissionResponse{
|
||||
UID: req.UID,
|
||||
Allowed: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return &admissionv1.AdmissionResponse{
|
||||
UID: req.UID,
|
||||
Allowed: true,
|
||||
}
|
||||
}
|
||||
|
||||
func (r *admissionTypeChecker) MarkerReceived() {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
r.upOnce.Do(func() {
|
||||
close(r.upCh)
|
||||
})
|
||||
}
|
||||
|
||||
func newAdmissionTypeCheckerHandler(recorder *admissionTypeChecker) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer r.Body.Close()
|
||||
data, err := io.ReadAll(r.Body)
|
||||
if err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
}
|
||||
review := admissionv1.AdmissionReview{}
|
||||
if err := json.Unmarshal(data, &review); err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
}
|
||||
|
||||
switch r.URL.Path {
|
||||
case "/marker":
|
||||
recorder.MarkerReceived()
|
||||
return
|
||||
case "/v1":
|
||||
review.Response = recorder.TypeCheck(review.Request, "v1")
|
||||
case "/v2":
|
||||
review.Response = recorder.TypeCheck(review.Request, "v2")
|
||||
}
|
||||
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
if err := json.NewEncoder(w).Encode(review); err != nil {
|
||||
http.Error(w, err.Error(), 400)
|
||||
return
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// Test_MutatingWebhookConvertsGVKWithMatchPolicyEquivalent tests if a equivalent resource is properly converted between mutating webhooks
|
||||
func Test_MutatingWebhookConvertsGVKWithMatchPolicyEquivalent(t *testing.T) {
|
||||
|
||||
roots := x509.NewCertPool()
|
||||
if !roots.AppendCertsFromPEM(localhostCert) {
|
||||
t.Fatal("Failed to append Cert from PEM")
|
||||
}
|
||||
cert, err := tls.X509KeyPair(localhostCert, localhostKey)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to build cert with error: %+v", err)
|
||||
}
|
||||
|
||||
typeChecker := &admissionTypeChecker{}
|
||||
|
||||
webhookServer := httptest.NewUnstartedServer(newAdmissionTypeCheckerHandler(typeChecker))
|
||||
webhookServer.TLS = &tls.Config{
|
||||
RootCAs: roots,
|
||||
Certificates: []tls.Certificate{cert},
|
||||
}
|
||||
webhookServer.StartTLS()
|
||||
defer webhookServer.Close()
|
||||
|
||||
upCh := typeChecker.Reset()
|
||||
defer featuregatetesting.SetFeatureGateDuringTest(t, utilfeature.DefaultFeatureGate, genericfeatures.AdmissionWebhookMatchConditions, true)()
|
||||
server, err := apiservertesting.StartTestServer(t, nil, []string{
|
||||
"--disable-admission-plugins=ServiceAccount",
|
||||
}, framework.SharedEtcd())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer server.TearDownFn()
|
||||
|
||||
etcd.CreateTestCRDs(t, apiextensionsclientset.NewForConfigOrDie(server.ClientConfig), false, versionedCustomResourceDefinition())
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
config := server.ClientConfig
|
||||
|
||||
client, err := clientset.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Write markers to a separate namespace to avoid cross-talk
|
||||
markerNs := "marker"
|
||||
_, err = client.CoreV1().Namespaces().Create(context.TODO(), &corev1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: markerNs}}, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// Create a marker object to use to check for the webhook configurations to be ready.
|
||||
marker, err := client.CoreV1().Pods(markerNs).Create(context.TODO(), newMarkerPodGVKConversion(markerNs), metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
equivalent := admissionregistrationv1.Equivalent
|
||||
ignore := admissionregistrationv1.Ignore
|
||||
|
||||
v1Endpoint := webhookServer.URL + "/v1"
|
||||
markerEndpoint := webhookServer.URL + "/marker"
|
||||
v2Endpoint := webhookServer.URL + "/v2"
|
||||
mutatingWebhook := &admissionregistrationv1.MutatingWebhookConfiguration{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "admission.integration.test",
|
||||
},
|
||||
Webhooks: []admissionregistrationv1.MutatingWebhook{
|
||||
{
|
||||
Name: "admission.integration.test.v2",
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"awesome.example.com"},
|
||||
APIVersions: []string{"v2"},
|
||||
Resources: []string{"*/*"},
|
||||
},
|
||||
}},
|
||||
MatchPolicy: &equivalent,
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &v2Endpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
FailurePolicy: &ignore,
|
||||
SideEffects: &noSideEffects,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
MatchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "test-v2",
|
||||
Expression: "object.apiVersion == 'awesome.example.com/v2'",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "admission.integration.test",
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.Create},
|
||||
Rule: admissionregistrationv1.Rule{
|
||||
APIGroups: []string{"awesome.example.com"},
|
||||
APIVersions: []string{"v1"},
|
||||
Resources: []string{"*/*"},
|
||||
},
|
||||
}},
|
||||
MatchPolicy: &equivalent,
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &v1Endpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
SideEffects: &noSideEffects,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
MatchConditions: []admissionregistrationv1.MatchCondition{
|
||||
{
|
||||
Name: "test-v1",
|
||||
Expression: "object.apiVersion == 'awesome.example.com/v1'",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "admission.integration.test.marker",
|
||||
Rules: []admissionregistrationv1.RuleWithOperations{{
|
||||
Operations: []admissionregistrationv1.OperationType{admissionregistrationv1.OperationAll},
|
||||
Rule: admissionregistrationv1.Rule{APIGroups: []string{""}, APIVersions: []string{"v1"}, Resources: []string{"pods"}},
|
||||
}},
|
||||
ClientConfig: admissionregistrationv1.WebhookClientConfig{
|
||||
URL: &markerEndpoint,
|
||||
CABundle: localhostCert,
|
||||
},
|
||||
NamespaceSelector: &metav1.LabelSelector{MatchLabels: map[string]string{
|
||||
corev1.LabelMetadataName: "marker",
|
||||
}},
|
||||
ObjectSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"marker": "true"}},
|
||||
SideEffects: &noSideEffects,
|
||||
AdmissionReviewVersions: []string{"v1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
mutatingCfg, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(context.TODO(), mutatingWebhook, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer func() {
|
||||
err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Delete(context.TODO(), mutatingCfg.GetName(), metav1.DeleteOptions{})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}()
|
||||
|
||||
// wait until new webhook is called the first time
|
||||
if err := wait.PollImmediate(time.Millisecond*5, wait.ForeverTestTimeout, func() (bool, error) {
|
||||
_, err = client.CoreV1().Pods(markerNs).Patch(context.TODO(), marker.Name, types.JSONPatchType, []byte("[]"), metav1.PatchOptions{})
|
||||
select {
|
||||
case <-upCh:
|
||||
return true, nil
|
||||
default:
|
||||
t.Logf("Waiting for webhook to become effective, getting marker object: %v", err)
|
||||
return false, nil
|
||||
}
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
dynamicClient, err := dynamic.NewForConfig(config)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
v1Resource := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "awesome.example.com" + "/" + "v1",
|
||||
"kind": "Panda",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "v1-bears",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
v2Resource := &unstructured.Unstructured{
|
||||
Object: map[string]interface{}{
|
||||
"apiVersion": "awesome.example.com" + "/" + "v2",
|
||||
"kind": "Panda",
|
||||
"metadata": map[string]interface{}{
|
||||
"name": "v2-bears",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
_, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.example.com", Version: "v1", Resource: "pandas"}).Create(context.TODO(), v1Resource, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("error1 %v", err.Error())
|
||||
}
|
||||
|
||||
_, err = dynamicClient.Resource(schema.GroupVersionResource{Group: "awesome.example.com", Version: "v2", Resource: "pandas"}).Create(context.TODO(), v2Resource, metav1.CreateOptions{})
|
||||
if err != nil {
|
||||
t.Errorf("error2 %v", err.Error())
|
||||
}
|
||||
|
||||
if len(typeChecker.requests) != 4 {
|
||||
t.Errorf("expected 4 request got %v", len(typeChecker.requests))
|
||||
}
|
||||
}
|
||||
|
||||
func newMarkerPodGVKConversion(namespace string) *corev1.Pod {
|
||||
return &corev1.Pod{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Namespace: namespace,
|
||||
Name: "marker",
|
||||
Labels: map[string]string{
|
||||
"marker": "true",
|
||||
},
|
||||
},
|
||||
Spec: corev1.PodSpec{
|
||||
Containers: []corev1.Container{{
|
||||
Name: "fake-name",
|
||||
Image: "fakeimage",
|
||||
}},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Copied from etcd.GetCustomResourceDefinitionData
|
||||
func versionedCustomResourceDefinition() *apiextensionsv1.CustomResourceDefinition {
|
||||
return &apiextensionsv1.CustomResourceDefinition{
|
||||
ObjectMeta: metav1.ObjectMeta{
|
||||
Name: "pandas.awesome.example.com",
|
||||
},
|
||||
Spec: apiextensionsv1.CustomResourceDefinitionSpec{
|
||||
Group: "awesome.example.com",
|
||||
Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
|
||||
{
|
||||
Name: "v1",
|
||||
Served: true,
|
||||
Storage: true,
|
||||
Schema: fixtures.AllowAllSchema(),
|
||||
Subresources: &apiextensionsv1.CustomResourceSubresources{
|
||||
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
|
||||
Scale: &apiextensionsv1.CustomResourceSubresourceScale{
|
||||
SpecReplicasPath: ".spec.replicas",
|
||||
StatusReplicasPath: ".status.replicas",
|
||||
LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "v2",
|
||||
Served: true,
|
||||
Storage: false,
|
||||
Schema: fixtures.AllowAllSchema(),
|
||||
Subresources: &apiextensionsv1.CustomResourceSubresources{
|
||||
Status: &apiextensionsv1.CustomResourceSubresourceStatus{},
|
||||
Scale: &apiextensionsv1.CustomResourceSubresourceScale{
|
||||
SpecReplicasPath: ".spec.replicas",
|
||||
StatusReplicasPath: ".status.replicas",
|
||||
LabelSelectorPath: func() *string { path := ".status.selector"; return &path }(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
Scope: apiextensionsv1.ClusterScoped,
|
||||
Names: apiextensionsv1.CustomResourceDefinitionNames{
|
||||
Plural: "pandas",
|
||||
Kind: "Panda",
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
1
vendor/modules.txt
vendored
1
vendor/modules.txt
vendored
@ -1413,6 +1413,7 @@ k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alp
|
||||
k8s.io/apiserver/pkg/admission/plugin/webhook/errors
|
||||
k8s.io/apiserver/pkg/admission/plugin/webhook/generic
|
||||
k8s.io/apiserver/pkg/admission/plugin/webhook/initializer
|
||||
k8s.io/apiserver/pkg/admission/plugin/webhook/matchconditions
|
||||
k8s.io/apiserver/pkg/admission/plugin/webhook/mutating
|
||||
k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/namespace
|
||||
k8s.io/apiserver/pkg/admission/plugin/webhook/predicates/object
|
||||
|
Loading…
Reference in New Issue
Block a user