diff --git a/api/openapi-spec/swagger.json b/api/openapi-spec/swagger.json index 7712dde0abd..bf6d03873aa 100644 --- a/api/openapi-spec/swagger.json +++ b/api/openapi-spec/swagger.json @@ -1,5 +1,66 @@ { "definitions": { + "io.k8s.api.admissionregistration.v1beta1.MutatingWebhook": { + "description": "MutatingWebhook describes an admission webhook and the resources and operations it applies to.", + "properties": { + "admissionReviewVersions": { + "description": "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']`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "clientConfig": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig", + "description": "ClientConfig defines how to communicate with the hook. Required" + }, + "failurePolicy": { + "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + "type": "string" + }, + "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 \"Exact\"", + "type": "string" + }, + "name": { + "description": "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.", + "type": "string" + }, + "namespaceSelector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + }, + "objectSelector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + }, + "reinvocationPolicy": { + "description": "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\".", + "type": "string" + }, + "rules": { + "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.RuleWithOperations" + }, + "type": "array" + }, + "sideEffects": { + "description": "SideEffects states whether this webhookk 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 change 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.", + "type": "string" + }, + "timeoutSeconds": { + "description": "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.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name", + "clientConfig" + ], + "type": "object" + }, "io.k8s.api.admissionregistration.v1beta1.MutatingWebhookConfiguration": { "description": "MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.", "properties": { @@ -18,7 +79,7 @@ "webhooks": { "description": "Webhooks is a list of webhooks and the affected resources and operations.", "items": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.Webhook" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.MutatingWebhook" }, "type": "array", "x-kubernetes-patch-merge-key": "name", @@ -134,6 +195,63 @@ ], "type": "object" }, + "io.k8s.api.admissionregistration.v1beta1.ValidatingWebhook": { + "description": "ValidatingWebhook describes an admission webhook and the resources and operations it applies to.", + "properties": { + "admissionReviewVersions": { + "description": "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']`.", + "items": { + "type": "string" + }, + "type": "array" + }, + "clientConfig": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig", + "description": "ClientConfig defines how to communicate with the hook. Required" + }, + "failurePolicy": { + "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + "type": "string" + }, + "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 \"Exact\"", + "type": "string" + }, + "name": { + "description": "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.", + "type": "string" + }, + "namespaceSelector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." + }, + "objectSelector": { + "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", + "description": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything." + }, + "rules": { + "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "items": { + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.RuleWithOperations" + }, + "type": "array" + }, + "sideEffects": { + "description": "SideEffects states whether this webhookk 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 change 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.", + "type": "string" + }, + "timeoutSeconds": { + "description": "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.", + "format": "int32", + "type": "integer" + } + }, + "required": [ + "name", + "clientConfig" + ], + "type": "object" + }, "io.k8s.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration": { "description": "ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.", "properties": { @@ -152,7 +270,7 @@ "webhooks": { "description": "Webhooks is a list of webhooks and the affected resources and operations.", "items": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.Webhook" + "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.ValidatingWebhook" }, "type": "array", "x-kubernetes-patch-merge-key": "name", @@ -203,59 +321,6 @@ } ] }, - "io.k8s.api.admissionregistration.v1beta1.Webhook": { - "description": "Webhook describes an admission webhook and the resources and operations it applies to.", - "properties": { - "admissionReviewVersions": { - "description": "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']`.", - "items": { - "type": "string" - }, - "type": "array" - }, - "clientConfig": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig", - "description": "ClientConfig defines how to communicate with the hook. Required" - }, - "failurePolicy": { - "description": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", - "type": "string" - }, - "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 \"Exact\"", - "type": "string" - }, - "name": { - "description": "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.", - "type": "string" - }, - "namespaceSelector": { - "$ref": "#/definitions/io.k8s.apimachinery.pkg.apis.meta.v1.LabelSelector", - "description": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything." - }, - "rules": { - "description": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", - "items": { - "$ref": "#/definitions/io.k8s.api.admissionregistration.v1beta1.RuleWithOperations" - }, - "type": "array" - }, - "sideEffects": { - "description": "SideEffects states whether this webhookk 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 change 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.", - "type": "string" - }, - "timeoutSeconds": { - "description": "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.", - "format": "int32", - "type": "integer" - } - }, - "required": [ - "name", - "clientConfig" - ], - "type": "object" - }, "io.k8s.api.admissionregistration.v1beta1.WebhookClientConfig": { "description": "WebhookClientConfig contains the information to make a TLS connection with the webhook", "properties": { diff --git a/hack/.golint_failures b/hack/.golint_failures index 5b99623c5f2..795f37bf204 100644 --- a/hack/.golint_failures +++ b/hack/.golint_failures @@ -426,6 +426,7 @@ staging/src/k8s.io/apimachinery/pkg/watch staging/src/k8s.io/apiserver/pkg/admission staging/src/k8s.io/apiserver/pkg/admission/configuration staging/src/k8s.io/apiserver/pkg/admission/initializer +staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1 staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts diff --git a/pkg/apis/admissionregistration/fuzzer/fuzzer.go b/pkg/apis/admissionregistration/fuzzer/fuzzer.go index 45f226ff655..86db2f44cab 100644 --- a/pkg/apis/admissionregistration/fuzzer/fuzzer.go +++ b/pkg/apis/admissionregistration/fuzzer/fuzzer.go @@ -33,7 +33,7 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { obj.Scope = &s } }, - func(obj *admissionregistration.Webhook, c fuzz.Continue) { + func(obj *admissionregistration.ValidatingWebhook, c fuzz.Continue) { c.FuzzNoCustom(obj) // fuzz self without calling this function again p := admissionregistration.FailurePolicyType("Fail") obj.FailurePolicy = &p @@ -47,5 +47,21 @@ var Funcs = func(codecs runtimeserializer.CodecFactory) []interface{} { } obj.AdmissionReviewVersions = []string{"v1beta1"} }, + func(obj *admissionregistration.MutatingWebhook, c fuzz.Continue) { + c.FuzzNoCustom(obj) // fuzz self without calling this function again + p := admissionregistration.FailurePolicyType("Fail") + obj.FailurePolicy = &p + m := admissionregistration.MatchPolicyType("Exact") + obj.MatchPolicy = &m + s := admissionregistration.SideEffectClassUnknown + obj.SideEffects = &s + n := admissionregistration.NeverReinvocationPolicy + obj.ReinvocationPolicy = &n + if obj.TimeoutSeconds == nil { + i := int32(30) + obj.TimeoutSeconds = &i + } + obj.AdmissionReviewVersions = []string{"v1beta1"} + }, } } diff --git a/pkg/apis/admissionregistration/types.go b/pkg/apis/admissionregistration/types.go index a17ceb205e0..78b1147c45a 100644 --- a/pkg/apis/admissionregistration/types.go +++ b/pkg/apis/admissionregistration/types.go @@ -123,7 +123,7 @@ type ValidatingWebhookConfiguration struct { metav1.ObjectMeta // Webhooks is a list of webhooks and the affected resources and operations. // +optional - Webhooks []Webhook + Webhooks []ValidatingWebhook } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -149,7 +149,7 @@ type MutatingWebhookConfiguration struct { metav1.ObjectMeta // Webhooks is a list of webhooks and the affected resources and operations. // +optional - Webhooks []Webhook + Webhooks []MutatingWebhook } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -165,8 +165,8 @@ type MutatingWebhookConfigurationList struct { Items []MutatingWebhookConfiguration } -// Webhook describes an admission webhook and the resources and operations it applies to. -type Webhook struct { +// ValidatingWebhook describes an admission webhook and the resources and operations it applies to. +type ValidatingWebhook struct { // 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 @@ -249,6 +249,20 @@ type Webhook struct { // +optional NamespaceSelector *metav1.LabelSelector + // ObjectSelector decides whether to run the webhook based on if the + // object has matching labels. objectSelector is evaluated against both + // the oldObject and newObject that would be sent to the webhook, and + // is considered to match if either object matches the selector. A null + // object (oldObject in the case of create, or newObject in the case of + // delete) or an object that cannot have labels (like a + // DeploymentRollback or a PodProxyOptions object) is not considered to + // match. + // Use the object selector only if the webhook is opt-in, because end + // users may skip the admission webhook by setting the labels. + // Default to the empty LabelSelector, which matches everything. + // +optional + ObjectSelector *metav1.LabelSelector + // SideEffects states whether this webhookk has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be @@ -275,6 +289,161 @@ type Webhook struct { AdmissionReviewVersions []string } +// MutatingWebhook describes an admission webhook and the resources and operations it applies to. +type MutatingWebhook struct { + // 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. + Name string + + // ClientConfig defines how to communicate with the hook. + // Required + ClientConfig WebhookClientConfig + + // Rules describes what operations on what resources/subresources the webhook cares about. + // The webhook cares about an operation if it matches _any_ Rule. + Rules []RuleWithOperations + + // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // allowed values are Ignore or Fail. Defaults to Ignore. + // +optional + FailurePolicy *FailurePolicyType + + // matchPolicy defines how the "rules" list is used to match incoming requests. + // Allowed values are "Exact" or "Equivalent". + // + // - 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. + // + // - 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. + // + // +optional + MatchPolicy *MatchPolicyType + + // NamespaceSelector decides whether to run the webhook on an object based + // on whether the namespace for that object matches the selector. If the + // object itself is a namespace, the matching is performed on + // object.metadata.labels. If the object is another cluster scoped resource, + // it never skips the webhook. + // + // For example, to run the webhook on any objects whose namespace is not + // associated with "runlevel" of "0" or "1"; you will set the selector as + // follows: + // "namespaceSelector": { + // "matchExpressions": [ + // { + // "key": "runlevel", + // "operator": "NotIn", + // "values": [ + // "0", + // "1" + // ] + // } + // ] + // } + // + // If instead you want to only run the webhook on any objects whose + // namespace is associated with the "environment" of "prod" or "staging"; + // you will set the selector as follows: + // "namespaceSelector": { + // "matchExpressions": [ + // { + // "key": "environment", + // "operator": "In", + // "values": [ + // "prod", + // "staging" + // ] + // } + // ] + // } + // + // See + // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // for more examples of label selectors. + // + // Default to the empty LabelSelector, which matches everything. + // +optional + NamespaceSelector *metav1.LabelSelector + + // ObjectSelector decides whether to run the webhook based on if the + // object has matching labels. objectSelector is evaluated against both + // the oldObject and newObject that would be sent to the webhook, and + // is considered to match if either object matches the selector. A null + // object (oldObject in the case of create, or newObject in the case of + // delete) or an object that cannot have labels (like a + // DeploymentRollback or a PodProxyOptions object) is not considered to + // match. + // Use the object selector only if the webhook is opt-in, because end + // users may skip the admission webhook by setting the labels. + // Default to the empty LabelSelector, which matches everything. + // +optional + ObjectSelector *metav1.LabelSelector + + // SideEffects states whether this webhookk 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 change 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. + // +optional + SideEffects *SideEffectClass + + // 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. + // +optional + TimeoutSeconds *int32 + + // 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 the webhook configuration has already been persisted with a version apiserver + // does not understand, calls to the webhook will fail and be subject to the failure policy. + // +optional + AdmissionReviewVersions []string + + // reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. + // Allowed values are "Never" and "IfNeeded". + // + // Never: the webhook will not be called more than once in a single admission evaluation. + // + // IfNeeded: 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, and hence 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. + // + // Defaults to "Never". + // +optional + ReinvocationPolicy *ReinvocationPolicyType +} + +// ReinvocationPolicyType specifies what type of policy the admission hook uses. +type ReinvocationPolicyType string + +var ( + // NeverReinvocationPolicy indicates that the webhook must not be called more than once in a + // single admission evaluation. + NeverReinvocationPolicy ReinvocationPolicyType = "Never" + // IfNeededReinvocationPolicy indicates that the webhook may 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. + IfNeededReinvocationPolicy ReinvocationPolicyType = "IfNeeded" +) + // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // sure that all the tuple expansions are valid. type RuleWithOperations struct { diff --git a/pkg/apis/admissionregistration/v1beta1/defaults.go b/pkg/apis/admissionregistration/v1beta1/defaults.go index 2ae7d62d247..2afa0642b76 100644 --- a/pkg/apis/admissionregistration/v1beta1/defaults.go +++ b/pkg/apis/admissionregistration/v1beta1/defaults.go @@ -27,7 +27,7 @@ func addDefaultingFuncs(scheme *runtime.Scheme) error { return RegisterDefaults(scheme) } -func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) { +func SetDefaults_ValidatingWebhook(obj *admissionregistrationv1beta1.ValidatingWebhook) { if obj.FailurePolicy == nil { policy := admissionregistrationv1beta1.Ignore obj.FailurePolicy = &policy @@ -40,6 +40,10 @@ func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) { selector := metav1.LabelSelector{} obj.NamespaceSelector = &selector } + if obj.ObjectSelector == nil { + selector := metav1.LabelSelector{} + obj.ObjectSelector = &selector + } if obj.SideEffects == nil { // TODO: revisit/remove this default and possibly make the field required when promoting to v1 unknown := admissionregistrationv1beta1.SideEffectClassUnknown @@ -55,6 +59,42 @@ func SetDefaults_Webhook(obj *admissionregistrationv1beta1.Webhook) { } } +func SetDefaults_MutatingWebhook(obj *admissionregistrationv1beta1.MutatingWebhook) { + if obj.FailurePolicy == nil { + policy := admissionregistrationv1beta1.Ignore + obj.FailurePolicy = &policy + } + if obj.MatchPolicy == nil { + policy := admissionregistrationv1beta1.Exact + obj.MatchPolicy = &policy + } + if obj.NamespaceSelector == nil { + selector := metav1.LabelSelector{} + obj.NamespaceSelector = &selector + } + if obj.ObjectSelector == nil { + selector := metav1.LabelSelector{} + obj.ObjectSelector = &selector + } + if obj.SideEffects == nil { + // TODO: revisit/remove this default and possibly make the field required when promoting to v1 + unknown := admissionregistrationv1beta1.SideEffectClassUnknown + obj.SideEffects = &unknown + } + if obj.TimeoutSeconds == nil { + obj.TimeoutSeconds = new(int32) + *obj.TimeoutSeconds = 30 + } + if obj.ReinvocationPolicy == nil { + never := admissionregistrationv1beta1.NeverReinvocationPolicy + obj.ReinvocationPolicy = &never + } + + if len(obj.AdmissionReviewVersions) == 0 { + obj.AdmissionReviewVersions = []string{admissionregistrationv1beta1.SchemeGroupVersion.Version} + } +} + func SetDefaults_Rule(obj *admissionregistrationv1beta1.Rule) { if obj.Scope == nil { s := admissionregistrationv1beta1.AllScopes diff --git a/pkg/apis/admissionregistration/v1beta1/zz_generated.conversion.go b/pkg/apis/admissionregistration/v1beta1/zz_generated.conversion.go index 516e2068f7b..cc105ce3812 100644 --- a/pkg/apis/admissionregistration/v1beta1/zz_generated.conversion.go +++ b/pkg/apis/admissionregistration/v1beta1/zz_generated.conversion.go @@ -37,6 +37,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.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 { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.MutatingWebhook)(nil), (*v1beta1.MutatingWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_MutatingWebhook_To_v1beta1_MutatingWebhook(a.(*admissionregistration.MutatingWebhook), b.(*v1beta1.MutatingWebhook), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*v1beta1.MutatingWebhookConfiguration)(nil), (*admissionregistration.MutatingWebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_MutatingWebhookConfiguration_To_admissionregistration_MutatingWebhookConfiguration(a.(*v1beta1.MutatingWebhookConfiguration), b.(*admissionregistration.MutatingWebhookConfiguration), scope) }); err != nil { @@ -87,6 +97,16 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } + if err := s.AddGeneratedConversionFunc((*v1beta1.ValidatingWebhook)(nil), (*admissionregistration.ValidatingWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_v1beta1_ValidatingWebhook_To_admissionregistration_ValidatingWebhook(a.(*v1beta1.ValidatingWebhook), b.(*admissionregistration.ValidatingWebhook), scope) + }); err != nil { + return err + } + if err := s.AddGeneratedConversionFunc((*admissionregistration.ValidatingWebhook)(nil), (*v1beta1.ValidatingWebhook)(nil), func(a, b interface{}, scope conversion.Scope) error { + return Convert_admissionregistration_ValidatingWebhook_To_v1beta1_ValidatingWebhook(a.(*admissionregistration.ValidatingWebhook), b.(*v1beta1.ValidatingWebhook), scope) + }); err != nil { + return err + } if err := s.AddGeneratedConversionFunc((*v1beta1.ValidatingWebhookConfiguration)(nil), (*admissionregistration.ValidatingWebhookConfiguration)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_ValidatingWebhookConfiguration_To_admissionregistration_ValidatingWebhookConfiguration(a.(*v1beta1.ValidatingWebhookConfiguration), b.(*admissionregistration.ValidatingWebhookConfiguration), scope) }); err != nil { @@ -107,16 +127,6 @@ func RegisterConversions(s *runtime.Scheme) error { }); err != nil { return err } - if err := s.AddGeneratedConversionFunc((*v1beta1.Webhook)(nil), (*admissionregistration.Webhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_v1beta1_Webhook_To_admissionregistration_Webhook(a.(*v1beta1.Webhook), b.(*admissionregistration.Webhook), scope) - }); err != nil { - return err - } - if err := s.AddGeneratedConversionFunc((*admissionregistration.Webhook)(nil), (*v1beta1.Webhook)(nil), func(a, b interface{}, scope conversion.Scope) error { - return Convert_admissionregistration_Webhook_To_v1beta1_Webhook(a.(*admissionregistration.Webhook), b.(*v1beta1.Webhook), scope) - }); err != nil { - return err - } if err := s.AddGeneratedConversionFunc((*v1beta1.WebhookClientConfig)(nil), (*admissionregistration.WebhookClientConfig)(nil), func(a, b interface{}, scope conversion.Scope) error { return Convert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(a.(*v1beta1.WebhookClientConfig), b.(*admissionregistration.WebhookClientConfig), scope) }); err != nil { @@ -130,13 +140,57 @@ func RegisterConversions(s *runtime.Scheme) error { return nil } +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 { + return err + } + out.Rules = *(*[]admissionregistration.RuleWithOperations)(unsafe.Pointer(&in.Rules)) + out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) + out.MatchPolicy = (*admissionregistration.MatchPolicyType)(unsafe.Pointer(in.MatchPolicy)) + out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) + out.ObjectSelector = (*v1.LabelSelector)(unsafe.Pointer(in.ObjectSelector)) + out.SideEffects = (*admissionregistration.SideEffectClass)(unsafe.Pointer(in.SideEffects)) + out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) + out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions)) + out.ReinvocationPolicy = (*admissionregistration.ReinvocationPolicyType)(unsafe.Pointer(in.ReinvocationPolicy)) + return nil +} + +// Convert_v1beta1_MutatingWebhook_To_admissionregistration_MutatingWebhook is an autogenerated conversion function. +func Convert_v1beta1_MutatingWebhook_To_admissionregistration_MutatingWebhook(in *v1beta1.MutatingWebhook, out *admissionregistration.MutatingWebhook, s conversion.Scope) error { + return autoConvert_v1beta1_MutatingWebhook_To_admissionregistration_MutatingWebhook(in, out, s) +} + +func autoConvert_admissionregistration_MutatingWebhook_To_v1beta1_MutatingWebhook(in *admissionregistration.MutatingWebhook, out *v1beta1.MutatingWebhook, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { + return err + } + out.Rules = *(*[]v1beta1.RuleWithOperations)(unsafe.Pointer(&in.Rules)) + out.FailurePolicy = (*v1beta1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) + out.MatchPolicy = (*v1beta1.MatchPolicyType)(unsafe.Pointer(in.MatchPolicy)) + out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) + out.ObjectSelector = (*v1.LabelSelector)(unsafe.Pointer(in.ObjectSelector)) + out.SideEffects = (*v1beta1.SideEffectClass)(unsafe.Pointer(in.SideEffects)) + out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) + out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions)) + out.ReinvocationPolicy = (*v1beta1.ReinvocationPolicyType)(unsafe.Pointer(in.ReinvocationPolicy)) + return nil +} + +// Convert_admissionregistration_MutatingWebhook_To_v1beta1_MutatingWebhook is an autogenerated conversion function. +func Convert_admissionregistration_MutatingWebhook_To_v1beta1_MutatingWebhook(in *admissionregistration.MutatingWebhook, out *v1beta1.MutatingWebhook, s conversion.Scope) error { + return autoConvert_admissionregistration_MutatingWebhook_To_v1beta1_MutatingWebhook(in, out, s) +} + func autoConvert_v1beta1_MutatingWebhookConfiguration_To_admissionregistration_MutatingWebhookConfiguration(in *v1beta1.MutatingWebhookConfiguration, out *admissionregistration.MutatingWebhookConfiguration, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if in.Webhooks != nil { in, out := &in.Webhooks, &out.Webhooks - *out = make([]admissionregistration.Webhook, len(*in)) + *out = make([]admissionregistration.MutatingWebhook, len(*in)) for i := range *in { - if err := Convert_v1beta1_Webhook_To_admissionregistration_Webhook(&(*in)[i], &(*out)[i], s); err != nil { + if err := Convert_v1beta1_MutatingWebhook_To_admissionregistration_MutatingWebhook(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -155,9 +209,9 @@ func autoConvert_admissionregistration_MutatingWebhookConfiguration_To_v1beta1_M out.ObjectMeta = in.ObjectMeta if in.Webhooks != nil { in, out := &in.Webhooks, &out.Webhooks - *out = make([]v1beta1.Webhook, len(*in)) + *out = make([]v1beta1.MutatingWebhook, len(*in)) for i := range *in { - if err := Convert_admissionregistration_Webhook_To_v1beta1_Webhook(&(*in)[i], &(*out)[i], s); err != nil { + if err := Convert_admissionregistration_MutatingWebhook_To_v1beta1_MutatingWebhook(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -296,13 +350,55 @@ func Convert_admissionregistration_ServiceReference_To_v1beta1_ServiceReference( return autoConvert_admissionregistration_ServiceReference_To_v1beta1_ServiceReference(in, out, s) } +func autoConvert_v1beta1_ValidatingWebhook_To_admissionregistration_ValidatingWebhook(in *v1beta1.ValidatingWebhook, out *admissionregistration.ValidatingWebhook, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { + return err + } + out.Rules = *(*[]admissionregistration.RuleWithOperations)(unsafe.Pointer(&in.Rules)) + out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) + out.MatchPolicy = (*admissionregistration.MatchPolicyType)(unsafe.Pointer(in.MatchPolicy)) + out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) + out.ObjectSelector = (*v1.LabelSelector)(unsafe.Pointer(in.ObjectSelector)) + out.SideEffects = (*admissionregistration.SideEffectClass)(unsafe.Pointer(in.SideEffects)) + out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) + out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions)) + return nil +} + +// Convert_v1beta1_ValidatingWebhook_To_admissionregistration_ValidatingWebhook is an autogenerated conversion function. +func Convert_v1beta1_ValidatingWebhook_To_admissionregistration_ValidatingWebhook(in *v1beta1.ValidatingWebhook, out *admissionregistration.ValidatingWebhook, s conversion.Scope) error { + return autoConvert_v1beta1_ValidatingWebhook_To_admissionregistration_ValidatingWebhook(in, out, s) +} + +func autoConvert_admissionregistration_ValidatingWebhook_To_v1beta1_ValidatingWebhook(in *admissionregistration.ValidatingWebhook, out *v1beta1.ValidatingWebhook, s conversion.Scope) error { + out.Name = in.Name + if err := Convert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { + return err + } + out.Rules = *(*[]v1beta1.RuleWithOperations)(unsafe.Pointer(&in.Rules)) + out.FailurePolicy = (*v1beta1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) + out.MatchPolicy = (*v1beta1.MatchPolicyType)(unsafe.Pointer(in.MatchPolicy)) + out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) + out.ObjectSelector = (*v1.LabelSelector)(unsafe.Pointer(in.ObjectSelector)) + out.SideEffects = (*v1beta1.SideEffectClass)(unsafe.Pointer(in.SideEffects)) + out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) + out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions)) + return nil +} + +// Convert_admissionregistration_ValidatingWebhook_To_v1beta1_ValidatingWebhook is an autogenerated conversion function. +func Convert_admissionregistration_ValidatingWebhook_To_v1beta1_ValidatingWebhook(in *admissionregistration.ValidatingWebhook, out *v1beta1.ValidatingWebhook, s conversion.Scope) error { + return autoConvert_admissionregistration_ValidatingWebhook_To_v1beta1_ValidatingWebhook(in, out, s) +} + func autoConvert_v1beta1_ValidatingWebhookConfiguration_To_admissionregistration_ValidatingWebhookConfiguration(in *v1beta1.ValidatingWebhookConfiguration, out *admissionregistration.ValidatingWebhookConfiguration, s conversion.Scope) error { out.ObjectMeta = in.ObjectMeta if in.Webhooks != nil { in, out := &in.Webhooks, &out.Webhooks - *out = make([]admissionregistration.Webhook, len(*in)) + *out = make([]admissionregistration.ValidatingWebhook, len(*in)) for i := range *in { - if err := Convert_v1beta1_Webhook_To_admissionregistration_Webhook(&(*in)[i], &(*out)[i], s); err != nil { + if err := Convert_v1beta1_ValidatingWebhook_To_admissionregistration_ValidatingWebhook(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -321,9 +417,9 @@ func autoConvert_admissionregistration_ValidatingWebhookConfiguration_To_v1beta1 out.ObjectMeta = in.ObjectMeta if in.Webhooks != nil { in, out := &in.Webhooks, &out.Webhooks - *out = make([]v1beta1.Webhook, len(*in)) + *out = make([]v1beta1.ValidatingWebhook, len(*in)) for i := range *in { - if err := Convert_admissionregistration_Webhook_To_v1beta1_Webhook(&(*in)[i], &(*out)[i], s); err != nil { + if err := Convert_admissionregistration_ValidatingWebhook_To_v1beta1_ValidatingWebhook(&(*in)[i], &(*out)[i], s); err != nil { return err } } @@ -380,46 +476,6 @@ func Convert_admissionregistration_ValidatingWebhookConfigurationList_To_v1beta1 return autoConvert_admissionregistration_ValidatingWebhookConfigurationList_To_v1beta1_ValidatingWebhookConfigurationList(in, out, s) } -func autoConvert_v1beta1_Webhook_To_admissionregistration_Webhook(in *v1beta1.Webhook, out *admissionregistration.Webhook, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { - return err - } - out.Rules = *(*[]admissionregistration.RuleWithOperations)(unsafe.Pointer(&in.Rules)) - out.FailurePolicy = (*admissionregistration.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) - out.MatchPolicy = (*admissionregistration.MatchPolicyType)(unsafe.Pointer(in.MatchPolicy)) - out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) - out.SideEffects = (*admissionregistration.SideEffectClass)(unsafe.Pointer(in.SideEffects)) - out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) - out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions)) - return nil -} - -// Convert_v1beta1_Webhook_To_admissionregistration_Webhook is an autogenerated conversion function. -func Convert_v1beta1_Webhook_To_admissionregistration_Webhook(in *v1beta1.Webhook, out *admissionregistration.Webhook, s conversion.Scope) error { - return autoConvert_v1beta1_Webhook_To_admissionregistration_Webhook(in, out, s) -} - -func autoConvert_admissionregistration_Webhook_To_v1beta1_Webhook(in *admissionregistration.Webhook, out *v1beta1.Webhook, s conversion.Scope) error { - out.Name = in.Name - if err := Convert_admissionregistration_WebhookClientConfig_To_v1beta1_WebhookClientConfig(&in.ClientConfig, &out.ClientConfig, s); err != nil { - return err - } - out.Rules = *(*[]v1beta1.RuleWithOperations)(unsafe.Pointer(&in.Rules)) - out.FailurePolicy = (*v1beta1.FailurePolicyType)(unsafe.Pointer(in.FailurePolicy)) - out.MatchPolicy = (*v1beta1.MatchPolicyType)(unsafe.Pointer(in.MatchPolicy)) - out.NamespaceSelector = (*v1.LabelSelector)(unsafe.Pointer(in.NamespaceSelector)) - out.SideEffects = (*v1beta1.SideEffectClass)(unsafe.Pointer(in.SideEffects)) - out.TimeoutSeconds = (*int32)(unsafe.Pointer(in.TimeoutSeconds)) - out.AdmissionReviewVersions = *(*[]string)(unsafe.Pointer(&in.AdmissionReviewVersions)) - return nil -} - -// Convert_admissionregistration_Webhook_To_v1beta1_Webhook is an autogenerated conversion function. -func Convert_admissionregistration_Webhook_To_v1beta1_Webhook(in *admissionregistration.Webhook, out *v1beta1.Webhook, s conversion.Scope) error { - return autoConvert_admissionregistration_Webhook_To_v1beta1_Webhook(in, out, s) -} - func autoConvert_v1beta1_WebhookClientConfig_To_admissionregistration_WebhookClientConfig(in *v1beta1.WebhookClientConfig, out *admissionregistration.WebhookClientConfig, s conversion.Scope) error { out.URL = (*string)(unsafe.Pointer(in.URL)) if in.Service != nil { diff --git a/pkg/apis/admissionregistration/v1beta1/zz_generated.defaults.go b/pkg/apis/admissionregistration/v1beta1/zz_generated.defaults.go index 8c49d7a9822..8159b2c9583 100644 --- a/pkg/apis/admissionregistration/v1beta1/zz_generated.defaults.go +++ b/pkg/apis/admissionregistration/v1beta1/zz_generated.defaults.go @@ -47,7 +47,7 @@ func RegisterDefaults(scheme *runtime.Scheme) error { func SetObjectDefaults_MutatingWebhookConfiguration(in *v1beta1.MutatingWebhookConfiguration) { for i := range in.Webhooks { a := &in.Webhooks[i] - SetDefaults_Webhook(a) + SetDefaults_MutatingWebhook(a) if a.ClientConfig.Service != nil { SetDefaults_ServiceReference(a.ClientConfig.Service) } @@ -68,7 +68,7 @@ func SetObjectDefaults_MutatingWebhookConfigurationList(in *v1beta1.MutatingWebh func SetObjectDefaults_ValidatingWebhookConfiguration(in *v1beta1.ValidatingWebhookConfiguration) { for i := range in.Webhooks { a := &in.Webhooks[i] - SetDefaults_Webhook(a) + SetDefaults_ValidatingWebhook(a) if a.ClientConfig.Service != nil { SetDefaults_ServiceReference(a.ClientConfig.Service) } diff --git a/pkg/apis/admissionregistration/validation/validation.go b/pkg/apis/admissionregistration/validation/validation.go index 5cb804a420e..ae34a214c43 100644 --- a/pkg/apis/admissionregistration/validation/validation.go +++ b/pkg/apis/admissionregistration/validation/validation.go @@ -201,7 +201,7 @@ func ValidateValidatingWebhookConfiguration(e *admissionregistration.ValidatingW func validateValidatingWebhookConfiguration(e *admissionregistration.ValidatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList { allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) for i, hook := range e.Webhooks { - allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) + allErrors = append(allErrors, validateValidatingWebhook(&hook, field.NewPath("webhooks").Index(i))...) allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...) } return allErrors @@ -214,13 +214,13 @@ func ValidateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebho func validateMutatingWebhookConfiguration(e *admissionregistration.MutatingWebhookConfiguration, requireRecognizedVersion bool) field.ErrorList { allErrors := genericvalidation.ValidateObjectMeta(&e.ObjectMeta, false, genericvalidation.NameIsDNSSubdomain, field.NewPath("metadata")) for i, hook := range e.Webhooks { - allErrors = append(allErrors, validateWebhook(&hook, field.NewPath("webhooks").Index(i))...) + allErrors = append(allErrors, validateMutatingWebhook(&hook, field.NewPath("webhooks").Index(i))...) allErrors = append(allErrors, validateAdmissionReviewVersions(hook.AdmissionReviewVersions, requireRecognizedVersion, field.NewPath("webhooks").Index(i).Child("admissionReviewVersions"))...) } return allErrors } -func validateWebhook(hook *admissionregistration.Webhook, fldPath *field.Path) field.ErrorList { +func validateValidatingWebhook(hook *admissionregistration.ValidatingWebhook, fldPath *field.Path) field.ErrorList { var allErrors field.ErrorList // hook.Name must be fully qualified allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...) @@ -245,6 +245,53 @@ func validateWebhook(hook *admissionregistration.Webhook, fldPath *field.Path) f allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...) } + if hook.ObjectSelector != nil { + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, fldPath.Child("objectSelector"))...) + } + + cc := hook.ClientConfig + switch { + case (cc.URL == nil) == (cc.Service == nil): + allErrors = append(allErrors, field.Required(fldPath.Child("clientConfig"), "exactly one of url or service is required")) + case cc.URL != nil: + allErrors = append(allErrors, webhook.ValidateWebhookURL(fldPath.Child("clientConfig").Child("url"), *cc.URL, true)...) + 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)...) + } + return allErrors +} + +func validateMutatingWebhook(hook *admissionregistration.MutatingWebhook, fldPath *field.Path) field.ErrorList { + var allErrors field.ErrorList + // hook.Name must be fully qualified + allErrors = append(allErrors, utilvalidation.IsFullyQualifiedName(fldPath.Child("name"), hook.Name)...) + + for i, rule := range hook.Rules { + allErrors = append(allErrors, validateRuleWithOperations(&rule, fldPath.Child("rules").Index(i))...) + } + if hook.FailurePolicy != nil && !supportedFailurePolicies.Has(string(*hook.FailurePolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("failurePolicy"), *hook.FailurePolicy, supportedFailurePolicies.List())) + } + if hook.MatchPolicy != nil && !supportedMatchPolicies.Has(string(*hook.MatchPolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("matchPolicy"), *hook.MatchPolicy, supportedMatchPolicies.List())) + } + if hook.SideEffects != nil && !supportedSideEffectClasses.Has(string(*hook.SideEffects)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("sideEffects"), *hook.SideEffects, supportedSideEffectClasses.List())) + } + if hook.TimeoutSeconds != nil && (*hook.TimeoutSeconds > 30 || *hook.TimeoutSeconds < 1) { + allErrors = append(allErrors, field.Invalid(fldPath.Child("timeoutSeconds"), *hook.TimeoutSeconds, "the timeout value must be between 1 and 30 seconds")) + } + + if hook.NamespaceSelector != nil { + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.NamespaceSelector, fldPath.Child("namespaceSelector"))...) + } + if hook.ObjectSelector != nil { + allErrors = append(allErrors, metav1validation.ValidateLabelSelector(hook.ObjectSelector, fldPath.Child("objectSelector"))...) + } + if hook.ReinvocationPolicy != nil && !supportedReinvocationPolicies.Has(string(*hook.ReinvocationPolicy)) { + allErrors = append(allErrors, field.NotSupported(fldPath.Child("reinvocationPolicy"), *hook.ReinvocationPolicy, supportedReinvocationPolicies.List())) + } + cc := hook.ClientConfig switch { case (cc.URL == nil) == (cc.Service == nil): @@ -282,6 +329,11 @@ var supportedOperations = sets.NewString( string(admissionregistration.Connect), ) +var supportedReinvocationPolicies = sets.NewString( + string(admissionregistration.NeverReinvocationPolicy), + string(admissionregistration.IfNeededReinvocationPolicy), +) + func hasWildcardOperation(operations []admissionregistration.OperationType) bool { for _, o := range operations { if o == admissionregistration.OperationAll { @@ -309,9 +361,27 @@ func validateRuleWithOperations(ruleWithOperations *admissionregistration.RuleWi return allErrors } -// hasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one +// mutatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one // admission review version this apiserver accepts. -func hasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.Webhook) bool { +func mutatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.MutatingWebhook) bool { + for _, hook := range webhooks { + hasRecognizedVersion := false + for _, version := range hook.AdmissionReviewVersions { + if isAcceptedAdmissionReviewVersion(version) { + hasRecognizedVersion = true + break + } + } + if !hasRecognizedVersion && len(hook.AdmissionReviewVersions) > 0 { + return false + } + } + return true +} + +// validatingHasAcceptedAdmissionReviewVersions returns true if all webhooks have at least one +// admission review version this apiserver accepts. +func validatingHasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.ValidatingWebhook) bool { for _, hook := range webhooks { hasRecognizedVersion := false for _, version := range hook.AdmissionReviewVersions { @@ -328,9 +398,9 @@ func hasAcceptedAdmissionReviewVersions(webhooks []admissionregistration.Webhook } func ValidateValidatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.ValidatingWebhookConfiguration) field.ErrorList { - return validateValidatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks)) + return validateValidatingWebhookConfiguration(newC, validatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks)) } func ValidateMutatingWebhookConfigurationUpdate(newC, oldC *admissionregistration.MutatingWebhookConfiguration) field.ErrorList { - return validateMutatingWebhookConfiguration(newC, hasAcceptedAdmissionReviewVersions(oldC.Webhooks)) + return validateMutatingWebhookConfiguration(newC, mutatingHasAcceptedAdmissionReviewVersions(oldC.Webhooks)) } diff --git a/pkg/apis/admissionregistration/validation/validation_test.go b/pkg/apis/admissionregistration/validation/validation_test.go index e206a549eec..73c08eda43b 100644 --- a/pkg/apis/admissionregistration/validation/validation_test.go +++ b/pkg/apis/admissionregistration/validation/validation_test.go @@ -28,7 +28,7 @@ func strPtr(s string) *string { return &s } func int32Ptr(i int32) *int32 { return &i } -func newValidatingWebhookConfiguration(hooks []admissionregistration.Webhook, defaultAdmissionReviewVersions bool) *admissionregistration.ValidatingWebhookConfiguration { +func newValidatingWebhookConfiguration(hooks []admissionregistration.ValidatingWebhook, defaultAdmissionReviewVersions bool) *admissionregistration.ValidatingWebhookConfiguration { // If the test case did not specify an AdmissionReviewVersions, default it so the test passes as // this field will be defaulted in production code. for i := range hooks { @@ -57,7 +57,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }{ { name: "should fail on bad AdmissionReviewVersion value", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -68,7 +68,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "should pass on valid AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -79,7 +79,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "should pass on mix of accepted and unaccepted AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -90,7 +90,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "should fail on invalid AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -101,7 +101,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "should fail on duplicate AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -112,7 +112,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "all Webhooks must have a fully qualified name", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -130,7 +130,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "Operations must not be empty or nil", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", Rules: []admissionregistration.RuleWithOperations{ @@ -157,7 +157,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "\"\" is NOT a valid operation", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", Rules: []admissionregistration.RuleWithOperations{ @@ -176,7 +176,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "operation must be either create/update/delete/connect", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", Rules: []admissionregistration.RuleWithOperations{ @@ -195,7 +195,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "wildcard operation cannot be mixed with other strings", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", Rules: []admissionregistration.RuleWithOperations{ @@ -214,7 +214,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: `resource "*" can co-exist with resources that have subresources`, - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -233,7 +233,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: `resource "*" cannot mix with resources that don't have subresources`, - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -253,7 +253,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "resource a/* cannot mix with a/x", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -273,7 +273,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "resource a/* can mix with a", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -292,7 +292,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "resource */a cannot mix with x/a", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -312,7 +312,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "resource */* cannot mix with other resources", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -332,7 +332,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "FailurePolicy can only be \"Ignore\" or \"Fail\"", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -346,7 +346,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "SideEffects can only be \"Unknown\", \"None\", \"Some\", or \"NoneOnDryRun\"", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -360,7 +360,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "both service and URL missing", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{}, @@ -370,7 +370,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "both service and URL provided", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -387,7 +387,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "blank URL", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -399,7 +399,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "wrong scheme", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -411,7 +411,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "missing host", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -423,7 +423,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "fragment", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -435,7 +435,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "query", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -447,7 +447,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "user", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -459,7 +459,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "just totally wrong", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -471,7 +471,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "path must start with slash", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -488,7 +488,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "path accepts slash", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -505,7 +505,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "path accepts no trailing slash", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -522,7 +522,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "path fails //", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -539,7 +539,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "path no empty step", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -555,7 +555,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { expectedError: `clientConfig.service.path: Invalid value: "/foo//bar/": segment[1] may not be empty`, }, { name: "path no empty step 2", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -572,7 +572,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "path no non-subdomain", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -590,7 +590,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { { name: "invalid port 0", config: newValidatingWebhookConfiguration( - []admissionregistration.Webhook{ + []admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -608,7 +608,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { { name: "invalid port >65535", config: newValidatingWebhookConfiguration( - []admissionregistration.Webhook{ + []admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: admissionregistration.WebhookClientConfig{ @@ -625,7 +625,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "timeout seconds cannot be greater than 30", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -636,7 +636,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "timeout seconds cannot be smaller than 1", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -647,7 +647,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "timeout seconds must be positive", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -658,7 +658,7 @@ func TestValidateValidatingWebhookConfiguration(t *testing.T) { }, { name: "valid timeout seconds", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -707,14 +707,14 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) { }{ { name: "should pass on valid new AdmissionReviewVersion", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, AdmissionReviewVersions: []string{"v1beta1"}, }, }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -724,14 +724,14 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) { }, { name: "should pass on invalid AdmissionReviewVersion with invalid previous versions", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, AdmissionReviewVersions: []string{"invalid-v1", "invalid-v2"}, }, }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -742,14 +742,14 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) { }, { name: "should fail on invalid AdmissionReviewVersion with valid previous versions", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, AdmissionReviewVersions: []string{"invalid-v1"}, }, }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, @@ -760,14 +760,14 @@ func TestValidateValidatingWebhookConfigurationUpdate(t *testing.T) { }, { name: "should fail on invalid AdmissionReviewVersion with missing previous versions", - config: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + config: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, AdmissionReviewVersions: []string{"invalid-v1"}, }, }, true), - oldconfig: newValidatingWebhookConfiguration([]admissionregistration.Webhook{ + oldconfig: newValidatingWebhookConfiguration([]admissionregistration.ValidatingWebhook{ { Name: "webhook.k8s.io", ClientConfig: validClientConfig, diff --git a/pkg/apis/admissionregistration/zz_generated.deepcopy.go b/pkg/apis/admissionregistration/zz_generated.deepcopy.go index 9e94111d6ea..44a84070d35 100644 --- a/pkg/apis/admissionregistration/zz_generated.deepcopy.go +++ b/pkg/apis/admissionregistration/zz_generated.deepcopy.go @@ -25,6 +25,70 @@ 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 *MutatingWebhook) DeepCopyInto(out *MutatingWebhook) { + *out = *in + in.ClientConfig.DeepCopyInto(&out.ClientConfig) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]RuleWithOperations, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + *out = new(FailurePolicyType) + **out = **in + } + if in.MatchPolicy != nil { + in, out := &in.MatchPolicy, &out.MatchPolicy + *out = new(MatchPolicyType) + **out = **in + } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.ObjectSelector != nil { + in, out := &in.ObjectSelector, &out.ObjectSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.SideEffects != nil { + in, out := &in.SideEffects, &out.SideEffects + *out = new(SideEffectClass) + **out = **in + } + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int32) + **out = **in + } + if in.AdmissionReviewVersions != nil { + in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ReinvocationPolicy != nil { + in, out := &in.ReinvocationPolicy, &out.ReinvocationPolicy + *out = new(ReinvocationPolicyType) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingWebhook. +func (in *MutatingWebhook) DeepCopy() *MutatingWebhook { + if in == nil { + return nil + } + out := new(MutatingWebhook) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MutatingWebhookConfiguration) DeepCopyInto(out *MutatingWebhookConfiguration) { *out = *in @@ -32,7 +96,7 @@ func (in *MutatingWebhookConfiguration) DeepCopyInto(out *MutatingWebhookConfigu in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) if in.Webhooks != nil { in, out := &in.Webhooks, &out.Webhooks - *out = make([]Webhook, len(*in)) + *out = make([]MutatingWebhook, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -170,6 +234,65 @@ func (in *ServiceReference) DeepCopy() *ServiceReference { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidatingWebhook) DeepCopyInto(out *ValidatingWebhook) { + *out = *in + in.ClientConfig.DeepCopyInto(&out.ClientConfig) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]RuleWithOperations, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + *out = new(FailurePolicyType) + **out = **in + } + if in.MatchPolicy != nil { + in, out := &in.MatchPolicy, &out.MatchPolicy + *out = new(MatchPolicyType) + **out = **in + } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.ObjectSelector != nil { + in, out := &in.ObjectSelector, &out.ObjectSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.SideEffects != nil { + in, out := &in.SideEffects, &out.SideEffects + *out = new(SideEffectClass) + **out = **in + } + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int32) + **out = **in + } + if in.AdmissionReviewVersions != nil { + in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingWebhook. +func (in *ValidatingWebhook) DeepCopy() *ValidatingWebhook { + if in == nil { + return nil + } + out := new(ValidatingWebhook) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ValidatingWebhookConfiguration) DeepCopyInto(out *ValidatingWebhookConfiguration) { *out = *in @@ -177,7 +300,7 @@ func (in *ValidatingWebhookConfiguration) DeepCopyInto(out *ValidatingWebhookCon in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) if in.Webhooks != nil { in, out := &in.Webhooks, &out.Webhooks - *out = make([]Webhook, len(*in)) + *out = make([]ValidatingWebhook, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -236,60 +359,6 @@ func (in *ValidatingWebhookConfigurationList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Webhook) DeepCopyInto(out *Webhook) { - *out = *in - in.ClientConfig.DeepCopyInto(&out.ClientConfig) - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = make([]RuleWithOperations, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.FailurePolicy != nil { - in, out := &in.FailurePolicy, &out.FailurePolicy - *out = new(FailurePolicyType) - **out = **in - } - if in.MatchPolicy != nil { - in, out := &in.MatchPolicy, &out.MatchPolicy - *out = new(MatchPolicyType) - **out = **in - } - if in.NamespaceSelector != nil { - in, out := &in.NamespaceSelector, &out.NamespaceSelector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - if in.SideEffects != nil { - in, out := &in.SideEffects, &out.SideEffects - *out = new(SideEffectClass) - **out = **in - } - if in.TimeoutSeconds != nil { - in, out := &in.TimeoutSeconds, &out.TimeoutSeconds - *out = new(int32) - **out = **in - } - if in.AdmissionReviewVersions != nil { - in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Webhook. -func (in *Webhook) DeepCopy() *Webhook { - if in == nil { - return nil - } - out := new(Webhook) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) { *out = *in diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.pb.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.pb.go index f3c73dc4824..b7ab68acb04 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.pb.go +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.pb.go @@ -24,14 +24,15 @@ limitations under the License. k8s.io/kubernetes/vendor/k8s.io/api/admissionregistration/v1beta1/generated.proto It has these top-level messages: + MutatingWebhook MutatingWebhookConfiguration MutatingWebhookConfigurationList Rule RuleWithOperations ServiceReference + ValidatingWebhook ValidatingWebhookConfiguration ValidatingWebhookConfigurationList - Webhook WebhookClientConfig */ package v1beta1 @@ -58,61 +59,172 @@ var _ = math.Inf // proto package needs to be updated. const _ = proto.GoGoProtoPackageIsVersion2 // please upgrade the proto package +func (m *MutatingWebhook) Reset() { *m = MutatingWebhook{} } +func (*MutatingWebhook) ProtoMessage() {} +func (*MutatingWebhook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{0} } + func (m *MutatingWebhookConfiguration) Reset() { *m = MutatingWebhookConfiguration{} } func (*MutatingWebhookConfiguration) ProtoMessage() {} func (*MutatingWebhookConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{0} + return fileDescriptorGenerated, []int{1} } func (m *MutatingWebhookConfigurationList) Reset() { *m = MutatingWebhookConfigurationList{} } func (*MutatingWebhookConfigurationList) ProtoMessage() {} func (*MutatingWebhookConfigurationList) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{1} + return fileDescriptorGenerated, []int{2} } func (m *Rule) Reset() { *m = Rule{} } func (*Rule) ProtoMessage() {} -func (*Rule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{2} } +func (*Rule) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } func (m *RuleWithOperations) Reset() { *m = RuleWithOperations{} } func (*RuleWithOperations) ProtoMessage() {} -func (*RuleWithOperations) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{3} } +func (*RuleWithOperations) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } func (m *ServiceReference) Reset() { *m = ServiceReference{} } func (*ServiceReference) ProtoMessage() {} -func (*ServiceReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{4} } +func (*ServiceReference) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{5} } + +func (m *ValidatingWebhook) Reset() { *m = ValidatingWebhook{} } +func (*ValidatingWebhook) ProtoMessage() {} +func (*ValidatingWebhook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{6} } func (m *ValidatingWebhookConfiguration) Reset() { *m = ValidatingWebhookConfiguration{} } func (*ValidatingWebhookConfiguration) ProtoMessage() {} func (*ValidatingWebhookConfiguration) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{5} + return fileDescriptorGenerated, []int{7} } func (m *ValidatingWebhookConfigurationList) Reset() { *m = ValidatingWebhookConfigurationList{} } func (*ValidatingWebhookConfigurationList) ProtoMessage() {} func (*ValidatingWebhookConfigurationList) Descriptor() ([]byte, []int) { - return fileDescriptorGenerated, []int{6} + return fileDescriptorGenerated, []int{8} } -func (m *Webhook) Reset() { *m = Webhook{} } -func (*Webhook) ProtoMessage() {} -func (*Webhook) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{7} } - func (m *WebhookClientConfig) Reset() { *m = WebhookClientConfig{} } func (*WebhookClientConfig) ProtoMessage() {} -func (*WebhookClientConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{8} } +func (*WebhookClientConfig) Descriptor() ([]byte, []int) { return fileDescriptorGenerated, []int{9} } func init() { + 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") proto.RegisterType((*Rule)(nil), "k8s.io.api.admissionregistration.v1beta1.Rule") proto.RegisterType((*RuleWithOperations)(nil), "k8s.io.api.admissionregistration.v1beta1.RuleWithOperations") proto.RegisterType((*ServiceReference)(nil), "k8s.io.api.admissionregistration.v1beta1.ServiceReference") + proto.RegisterType((*ValidatingWebhook)(nil), "k8s.io.api.admissionregistration.v1beta1.ValidatingWebhook") proto.RegisterType((*ValidatingWebhookConfiguration)(nil), "k8s.io.api.admissionregistration.v1beta1.ValidatingWebhookConfiguration") proto.RegisterType((*ValidatingWebhookConfigurationList)(nil), "k8s.io.api.admissionregistration.v1beta1.ValidatingWebhookConfigurationList") - proto.RegisterType((*Webhook)(nil), "k8s.io.api.admissionregistration.v1beta1.Webhook") proto.RegisterType((*WebhookClientConfig)(nil), "k8s.io.api.admissionregistration.v1beta1.WebhookClientConfig") } +func (m *MutatingWebhook) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *MutatingWebhook) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(m.Name))) + i += copy(dAtA[i:], m.Name) + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ClientConfig.Size())) + n1, err := m.ClientConfig.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n1 + if len(m.Rules) > 0 { + for _, msg := range m.Rules { + dAtA[i] = 0x1a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + if m.FailurePolicy != nil { + dAtA[i] = 0x22 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.FailurePolicy))) + i += copy(dAtA[i:], *m.FailurePolicy) + } + if m.NamespaceSelector != nil { + dAtA[i] = 0x2a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.NamespaceSelector.Size())) + n2, err := m.NamespaceSelector.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n2 + } + if m.SideEffects != nil { + dAtA[i] = 0x32 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.SideEffects))) + i += copy(dAtA[i:], *m.SideEffects) + } + if m.TimeoutSeconds != nil { + dAtA[i] = 0x38 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(*m.TimeoutSeconds)) + } + if len(m.AdmissionReviewVersions) > 0 { + for _, s := range m.AdmissionReviewVersions { + dAtA[i] = 0x42 + i++ + l = len(s) + for l >= 1<<7 { + dAtA[i] = uint8(uint64(l)&0x7f | 0x80) + l >>= 7 + i++ + } + dAtA[i] = uint8(l) + i++ + i += copy(dAtA[i:], s) + } + } + if m.MatchPolicy != nil { + dAtA[i] = 0x4a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.MatchPolicy))) + i += copy(dAtA[i:], *m.MatchPolicy) + } + if m.ReinvocationPolicy != nil { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(len(*m.ReinvocationPolicy))) + i += copy(dAtA[i:], *m.ReinvocationPolicy) + } + if m.ObjectSelector != nil { + dAtA[i] = 0x5a + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectSelector.Size())) + n3, err := m.ObjectSelector.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n3 + } + return i, nil +} + func (m *MutatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) @@ -131,11 +243,11 @@ func (m *MutatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n1, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + n4, err := m.ObjectMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n1 + i += n4 if len(m.Webhooks) > 0 { for _, msg := range m.Webhooks { dAtA[i] = 0x12 @@ -169,11 +281,11 @@ func (m *MutatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n2, err := m.ListMeta.MarshalTo(dAtA[i:]) + n5, err := m.ListMeta.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n2 + i += n5 if len(m.Items) > 0 { for _, msg := range m.Items { dAtA[i] = 0x12 @@ -291,11 +403,11 @@ func (m *RuleWithOperations) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Rule.Size())) - n3, err := m.Rule.MarshalTo(dAtA[i:]) + n6, err := m.Rule.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n3 + i += n6 return i, nil } @@ -336,7 +448,7 @@ func (m *ServiceReference) MarshalTo(dAtA []byte) (int, error) { return i, nil } -func (m *ValidatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { +func (m *ValidatingWebhook) Marshal() (dAtA []byte, err error) { size := m.Size() dAtA = make([]byte, size) n, err := m.MarshalTo(dAtA) @@ -346,83 +458,7 @@ func (m *ValidatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { return dAtA[:n], nil } -func (m *ValidatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) - n4, err := m.ObjectMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n4 - if len(m.Webhooks) > 0 { - for _, msg := range m.Webhooks { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *ValidatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *ValidatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { - var i int - _ = i - var l int - _ = l - dAtA[i] = 0xa - i++ - i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) - n5, err := m.ListMeta.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n5 - if len(m.Items) > 0 { - for _, msg := range m.Items { - dAtA[i] = 0x12 - i++ - i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) - n, err := msg.MarshalTo(dAtA[i:]) - if err != nil { - return 0, err - } - i += n - } - } - return i, nil -} - -func (m *Webhook) Marshal() (dAtA []byte, err error) { - size := m.Size() - dAtA = make([]byte, size) - n, err := m.MarshalTo(dAtA) - if err != nil { - return nil, err - } - return dAtA[:n], nil -} - -func (m *Webhook) MarshalTo(dAtA []byte) (int, error) { +func (m *ValidatingWebhook) MarshalTo(dAtA []byte) (int, error) { var i int _ = i var l int @@ -434,11 +470,11 @@ func (m *Webhook) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x12 i++ i = encodeVarintGenerated(dAtA, i, uint64(m.ClientConfig.Size())) - n6, err := m.ClientConfig.MarshalTo(dAtA[i:]) + n7, err := m.ClientConfig.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n6 + i += n7 if len(m.Rules) > 0 { for _, msg := range m.Rules { dAtA[i] = 0x1a @@ -461,11 +497,11 @@ func (m *Webhook) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0x2a i++ i = encodeVarintGenerated(dAtA, i, uint64(m.NamespaceSelector.Size())) - n7, err := m.NamespaceSelector.MarshalTo(dAtA[i:]) + n8, err := m.NamespaceSelector.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n7 + i += n8 } if m.SideEffects != nil { dAtA[i] = 0x32 @@ -499,6 +535,92 @@ func (m *Webhook) MarshalTo(dAtA []byte) (int, error) { i = encodeVarintGenerated(dAtA, i, uint64(len(*m.MatchPolicy))) i += copy(dAtA[i:], *m.MatchPolicy) } + if m.ObjectSelector != nil { + dAtA[i] = 0x52 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectSelector.Size())) + n9, err := m.ObjectSelector.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n9 + } + return i, nil +} + +func (m *ValidatingWebhookConfiguration) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ValidatingWebhookConfiguration) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ObjectMeta.Size())) + n10, err := m.ObjectMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n10 + if len(m.Webhooks) > 0 { + for _, msg := range m.Webhooks { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } + return i, nil +} + +func (m *ValidatingWebhookConfigurationList) Marshal() (dAtA []byte, err error) { + size := m.Size() + dAtA = make([]byte, size) + n, err := m.MarshalTo(dAtA) + if err != nil { + return nil, err + } + return dAtA[:n], nil +} + +func (m *ValidatingWebhookConfigurationList) MarshalTo(dAtA []byte) (int, error) { + var i int + _ = i + var l int + _ = l + dAtA[i] = 0xa + i++ + i = encodeVarintGenerated(dAtA, i, uint64(m.ListMeta.Size())) + n11, err := m.ListMeta.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n11 + if len(m.Items) > 0 { + for _, msg := range m.Items { + dAtA[i] = 0x12 + i++ + i = encodeVarintGenerated(dAtA, i, uint64(msg.Size())) + n, err := msg.MarshalTo(dAtA[i:]) + if err != nil { + return 0, err + } + i += n + } + } return i, nil } @@ -521,11 +643,11 @@ func (m *WebhookClientConfig) MarshalTo(dAtA []byte) (int, error) { dAtA[i] = 0xa i++ i = encodeVarintGenerated(dAtA, i, uint64(m.Service.Size())) - n8, err := m.Service.MarshalTo(dAtA[i:]) + n12, err := m.Service.MarshalTo(dAtA[i:]) if err != nil { return 0, err } - i += n8 + i += n12 } if m.CABundle != nil { dAtA[i] = 0x12 @@ -551,6 +673,55 @@ func encodeVarintGenerated(dAtA []byte, offset int, v uint64) int { dAtA[offset] = uint8(v) return offset + 1 } +func (m *MutatingWebhook) Size() (n int) { + var l int + _ = l + l = len(m.Name) + n += 1 + l + sovGenerated(uint64(l)) + l = m.ClientConfig.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Rules) > 0 { + for _, e := range m.Rules { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.FailurePolicy != nil { + l = len(*m.FailurePolicy) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.NamespaceSelector != nil { + l = m.NamespaceSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + if m.SideEffects != nil { + l = len(*m.SideEffects) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.TimeoutSeconds != nil { + n += 1 + sovGenerated(uint64(*m.TimeoutSeconds)) + } + if len(m.AdmissionReviewVersions) > 0 { + for _, s := range m.AdmissionReviewVersions { + l = len(s) + n += 1 + l + sovGenerated(uint64(l)) + } + } + if m.MatchPolicy != nil { + l = len(*m.MatchPolicy) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.ReinvocationPolicy != nil { + l = len(*m.ReinvocationPolicy) + n += 1 + l + sovGenerated(uint64(l)) + } + if m.ObjectSelector != nil { + l = m.ObjectSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + func (m *MutatingWebhookConfiguration) Size() (n int) { var l int _ = l @@ -638,35 +809,7 @@ func (m *ServiceReference) Size() (n int) { return n } -func (m *ValidatingWebhookConfiguration) Size() (n int) { - var l int - _ = l - l = m.ObjectMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Webhooks) > 0 { - for _, e := range m.Webhooks { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *ValidatingWebhookConfigurationList) Size() (n int) { - var l int - _ = l - l = m.ListMeta.Size() - n += 1 + l + sovGenerated(uint64(l)) - if len(m.Items) > 0 { - for _, e := range m.Items { - l = e.Size() - n += 1 + l + sovGenerated(uint64(l)) - } - } - return n -} - -func (m *Webhook) Size() (n int) { +func (m *ValidatingWebhook) Size() (n int) { var l int _ = l l = len(m.Name) @@ -704,6 +847,38 @@ func (m *Webhook) Size() (n int) { l = len(*m.MatchPolicy) n += 1 + l + sovGenerated(uint64(l)) } + if m.ObjectSelector != nil { + l = m.ObjectSelector.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + return n +} + +func (m *ValidatingWebhookConfiguration) Size() (n int) { + var l int + _ = l + l = m.ObjectMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Webhooks) > 0 { + for _, e := range m.Webhooks { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } + return n +} + +func (m *ValidatingWebhookConfigurationList) Size() (n int) { + var l int + _ = l + l = m.ListMeta.Size() + n += 1 + l + sovGenerated(uint64(l)) + if len(m.Items) > 0 { + for _, e := range m.Items { + l = e.Size() + n += 1 + l + sovGenerated(uint64(l)) + } + } return n } @@ -738,13 +913,33 @@ func sovGenerated(x uint64) (n int) { func sozGenerated(x uint64) (n int) { return sovGenerated(uint64((x << 1) ^ uint64((int64(x) >> 63)))) } +func (this *MutatingWebhook) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&MutatingWebhook{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`, + `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + `,`, + `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, + `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, + `SideEffects:` + valueToStringGenerated(this.SideEffects) + `,`, + `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, + `AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`, + `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, + `ReinvocationPolicy:` + valueToStringGenerated(this.ReinvocationPolicy) + `,`, + `ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, + `}`, + }, "") + return s +} func (this *MutatingWebhookConfiguration) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&MutatingWebhookConfiguration{`, `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Webhooks:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Webhooks), "Webhook", "Webhook", 1), `&`, ``, 1) + `,`, + `Webhooks:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Webhooks), "MutatingWebhook", "MutatingWebhook", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -797,13 +992,32 @@ func (this *ServiceReference) String() string { }, "") return s } +func (this *ValidatingWebhook) String() string { + if this == nil { + return "nil" + } + s := strings.Join([]string{`&ValidatingWebhook{`, + `Name:` + fmt.Sprintf("%v", this.Name) + `,`, + `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`, + `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + `,`, + `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, + `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, + `SideEffects:` + valueToStringGenerated(this.SideEffects) + `,`, + `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, + `AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`, + `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, + `ObjectSelector:` + strings.Replace(fmt.Sprintf("%v", this.ObjectSelector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, + `}`, + }, "") + return s +} func (this *ValidatingWebhookConfiguration) String() string { if this == nil { return "nil" } s := strings.Join([]string{`&ValidatingWebhookConfiguration{`, `ObjectMeta:` + strings.Replace(strings.Replace(this.ObjectMeta.String(), "ObjectMeta", "k8s_io_apimachinery_pkg_apis_meta_v1.ObjectMeta", 1), `&`, ``, 1) + `,`, - `Webhooks:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Webhooks), "Webhook", "Webhook", 1), `&`, ``, 1) + `,`, + `Webhooks:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Webhooks), "ValidatingWebhook", "ValidatingWebhook", 1), `&`, ``, 1) + `,`, `}`, }, "") return s @@ -819,24 +1033,6 @@ func (this *ValidatingWebhookConfigurationList) String() string { }, "") return s } -func (this *Webhook) String() string { - if this == nil { - return "nil" - } - s := strings.Join([]string{`&Webhook{`, - `Name:` + fmt.Sprintf("%v", this.Name) + `,`, - `ClientConfig:` + strings.Replace(strings.Replace(this.ClientConfig.String(), "WebhookClientConfig", "WebhookClientConfig", 1), `&`, ``, 1) + `,`, - `Rules:` + strings.Replace(strings.Replace(fmt.Sprintf("%v", this.Rules), "RuleWithOperations", "RuleWithOperations", 1), `&`, ``, 1) + `,`, - `FailurePolicy:` + valueToStringGenerated(this.FailurePolicy) + `,`, - `NamespaceSelector:` + strings.Replace(fmt.Sprintf("%v", this.NamespaceSelector), "LabelSelector", "k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector", 1) + `,`, - `SideEffects:` + valueToStringGenerated(this.SideEffects) + `,`, - `TimeoutSeconds:` + valueToStringGenerated(this.TimeoutSeconds) + `,`, - `AdmissionReviewVersions:` + fmt.Sprintf("%v", this.AdmissionReviewVersions) + `,`, - `MatchPolicy:` + valueToStringGenerated(this.MatchPolicy) + `,`, - `}`, - }, "") - return s -} func (this *WebhookClientConfig) String() string { if this == nil { return "nil" @@ -857,6 +1053,381 @@ func valueToStringGenerated(v interface{}) string { pv := reflect.Indirect(rv).Interface() return fmt.Sprintf("*%v", pv) } +func (m *MutatingWebhook) 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: MutatingWebhook: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: MutatingWebhook: 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 > 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 ClientConfig", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.ClientConfig.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 3: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Rules", 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 > l { + return io.ErrUnexpectedEOF + } + m.Rules = append(m.Rules, RuleWithOperations{}) + if err := m.Rules[len(m.Rules)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 4: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field FailurePolicy", 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 > l { + return io.ErrUnexpectedEOF + } + s := FailurePolicyType(dAtA[iNdEx:postIndex]) + m.FailurePolicy = &s + iNdEx = postIndex + case 5: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field NamespaceSelector", 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 > l { + return io.ErrUnexpectedEOF + } + if m.NamespaceSelector == nil { + m.NamespaceSelector = &k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{} + } + if err := m.NamespaceSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 6: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field SideEffects", 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 > l { + return io.ErrUnexpectedEOF + } + s := SideEffectClass(dAtA[iNdEx:postIndex]) + m.SideEffects = &s + iNdEx = postIndex + case 7: + if wireType != 0 { + return fmt.Errorf("proto: wrong wireType = %d for field TimeoutSeconds", wireType) + } + var v int32 + for shift := uint(0); ; shift += 7 { + if shift >= 64 { + return ErrIntOverflowGenerated + } + if iNdEx >= l { + return io.ErrUnexpectedEOF + } + b := dAtA[iNdEx] + iNdEx++ + v |= (int32(b) & 0x7F) << shift + if b < 0x80 { + break + } + } + m.TimeoutSeconds = &v + case 8: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field AdmissionReviewVersions", 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 > l { + return io.ErrUnexpectedEOF + } + m.AdmissionReviewVersions = append(m.AdmissionReviewVersions, string(dAtA[iNdEx:postIndex])) + iNdEx = postIndex + case 9: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field MatchPolicy", 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 > l { + return io.ErrUnexpectedEOF + } + s := MatchPolicyType(dAtA[iNdEx:postIndex]) + m.MatchPolicy = &s + iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ReinvocationPolicy", 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 > l { + return io.ErrUnexpectedEOF + } + s := ReinvocationPolicyType(dAtA[iNdEx:postIndex]) + m.ReinvocationPolicy = &s + iNdEx = postIndex + case 11: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectSelector", 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 > l { + return io.ErrUnexpectedEOF + } + if m.ObjectSelector == nil { + m.ObjectSelector = &k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{} + } + if err := m.ObjectSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} func (m *MutatingWebhookConfiguration) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 @@ -942,7 +1513,7 @@ func (m *MutatingWebhookConfiguration) Unmarshal(dAtA []byte) error { if postIndex > l { return io.ErrUnexpectedEOF } - m.Webhooks = append(m.Webhooks, Webhook{}) + m.Webhooks = append(m.Webhooks, MutatingWebhook{}) if err := m.Webhooks[len(m.Webhooks)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { return err } @@ -1513,7 +2084,7 @@ func (m *ServiceReference) Unmarshal(dAtA []byte) error { } return nil } -func (m *ValidatingWebhookConfiguration) Unmarshal(dAtA []byte) error { +func (m *ValidatingWebhook) Unmarshal(dAtA []byte) error { l := len(dAtA) iNdEx := 0 for iNdEx < l { @@ -1536,232 +2107,10 @@ func (m *ValidatingWebhookConfiguration) Unmarshal(dAtA []byte) error { fieldNum := int32(wire >> 3) wireType := int(wire & 0x7) if wireType == 4 { - return fmt.Errorf("proto: ValidatingWebhookConfiguration: wiretype end group for non-group") + return fmt.Errorf("proto: ValidatingWebhook: wiretype end group for non-group") } if fieldNum <= 0 { - return fmt.Errorf("proto: ValidatingWebhookConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Webhooks", 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 > l { - return io.ErrUnexpectedEOF - } - m.Webhooks = append(m.Webhooks, Webhook{}) - if err := m.Webhooks[len(m.Webhooks)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *ValidatingWebhookConfigurationList) 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: ValidatingWebhookConfigurationList: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: ValidatingWebhookConfigurationList: illegal tag %d (wire type %d)", fieldNum, wire) - } - switch fieldNum { - case 1: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", 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 > l { - return io.ErrUnexpectedEOF - } - if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - case 2: - if wireType != 2 { - return fmt.Errorf("proto: wrong wireType = %d for field Items", 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 > l { - return io.ErrUnexpectedEOF - } - m.Items = append(m.Items, ValidatingWebhookConfiguration{}) - if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { - return err - } - iNdEx = postIndex - default: - iNdEx = preIndex - skippy, err := skipGenerated(dAtA[iNdEx:]) - if err != nil { - return err - } - if skippy < 0 { - return ErrInvalidLengthGenerated - } - if (iNdEx + skippy) > l { - return io.ErrUnexpectedEOF - } - iNdEx += skippy - } - } - - if iNdEx > l { - return io.ErrUnexpectedEOF - } - return nil -} -func (m *Webhook) 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: Webhook: wiretype end group for non-group") - } - if fieldNum <= 0 { - return fmt.Errorf("proto: Webhook: illegal tag %d (wire type %d)", fieldNum, wire) + return fmt.Errorf("proto: ValidatingWebhook: illegal tag %d (wire type %d)", fieldNum, wire) } switch fieldNum { case 1: @@ -2026,6 +2375,261 @@ func (m *Webhook) Unmarshal(dAtA []byte) error { s := MatchPolicyType(dAtA[iNdEx:postIndex]) m.MatchPolicy = &s iNdEx = postIndex + case 10: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectSelector", 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 > l { + return io.ErrUnexpectedEOF + } + if m.ObjectSelector == nil { + m.ObjectSelector = &k8s_io_apimachinery_pkg_apis_meta_v1.LabelSelector{} + } + if err := m.ObjectSelector.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ValidatingWebhookConfiguration) 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: ValidatingWebhookConfiguration: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ValidatingWebhookConfiguration: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ObjectMeta", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.ObjectMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Webhooks", 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 > l { + return io.ErrUnexpectedEOF + } + m.Webhooks = append(m.Webhooks, ValidatingWebhook{}) + if err := m.Webhooks[len(m.Webhooks)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + default: + iNdEx = preIndex + skippy, err := skipGenerated(dAtA[iNdEx:]) + if err != nil { + return err + } + if skippy < 0 { + return ErrInvalidLengthGenerated + } + if (iNdEx + skippy) > l { + return io.ErrUnexpectedEOF + } + iNdEx += skippy + } + } + + if iNdEx > l { + return io.ErrUnexpectedEOF + } + return nil +} +func (m *ValidatingWebhookConfigurationList) 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: ValidatingWebhookConfigurationList: wiretype end group for non-group") + } + if fieldNum <= 0 { + return fmt.Errorf("proto: ValidatingWebhookConfigurationList: illegal tag %d (wire type %d)", fieldNum, wire) + } + switch fieldNum { + case 1: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field ListMeta", 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 > l { + return io.ErrUnexpectedEOF + } + if err := m.ListMeta.Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex + case 2: + if wireType != 2 { + return fmt.Errorf("proto: wrong wireType = %d for field Items", 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 > l { + return io.ErrUnexpectedEOF + } + m.Items = append(m.Items, ValidatingWebhookConfiguration{}) + if err := m.Items[len(m.Items)-1].Unmarshal(dAtA[iNdEx:postIndex]); err != nil { + return err + } + iNdEx = postIndex default: iNdEx = preIndex skippy, err := skipGenerated(dAtA[iNdEx:]) @@ -2301,69 +2905,75 @@ func init() { } var fileDescriptorGenerated = []byte{ - // 1021 bytes of a gzipped FileDescriptorProto - 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xdc, 0x55, 0x4f, 0x6f, 0xe3, 0x44, - 0x14, 0xaf, 0x37, 0x09, 0x89, 0x27, 0xed, 0xee, 0x76, 0xf8, 0xb3, 0x61, 0x59, 0xc5, 0x51, 0x0e, - 0x28, 0x12, 0xac, 0x4d, 0x0b, 0x42, 0x68, 0x05, 0x42, 0x75, 0x61, 0xa1, 0x52, 0xbb, 0x5b, 0x26, - 0xfb, 0x47, 0x42, 0x1c, 0x98, 0x38, 0x2f, 0xc9, 0x90, 0xc4, 0x63, 0x79, 0xc6, 0x29, 0xbd, 0xf1, - 0x11, 0xf8, 0x0a, 0x9c, 0xf8, 0x14, 0x1c, 0xb8, 0xf5, 0xb8, 0x17, 0xc4, 0x9e, 0x2c, 0x6a, 0xce, - 0x20, 0x71, 0xed, 0x09, 0xcd, 0xd8, 0x89, 0x93, 0xa6, 0xed, 0x66, 0x2f, 0x1c, 0xb8, 0x79, 0x7e, - 0xef, 0xfd, 0xde, 0x7b, 0xbf, 0x99, 0xf7, 0x9e, 0xd1, 0x57, 0xc3, 0x8f, 0x84, 0xcd, 0xb8, 0x33, - 0x8c, 0x3a, 0x10, 0xfa, 0x20, 0x41, 0x38, 0x13, 0xf0, 0xbb, 0x3c, 0x74, 0x32, 0x03, 0x0d, 0x98, - 0x43, 0xbb, 0x63, 0x26, 0x04, 0xe3, 0x7e, 0x08, 0x7d, 0x26, 0x64, 0x48, 0x25, 0xe3, 0xbe, 0x33, - 0xd9, 0xea, 0x80, 0xa4, 0x5b, 0x4e, 0x1f, 0x7c, 0x08, 0xa9, 0x84, 0xae, 0x1d, 0x84, 0x5c, 0x72, - 0xdc, 0x4a, 0x99, 0x36, 0x0d, 0x98, 0x7d, 0x21, 0xd3, 0xce, 0x98, 0xb7, 0xef, 0xf6, 0x99, 0x1c, - 0x44, 0x1d, 0xdb, 0xe3, 0x63, 0xa7, 0xcf, 0xfb, 0xdc, 0xd1, 0x01, 0x3a, 0x51, 0x4f, 0x9f, 0xf4, - 0x41, 0x7f, 0xa5, 0x81, 0x6f, 0x7f, 0x90, 0x97, 0x34, 0xa6, 0xde, 0x80, 0xf9, 0x10, 0x1e, 0x3b, - 0xc1, 0xb0, 0xaf, 0x00, 0xe1, 0x8c, 0x41, 0x52, 0x67, 0xb2, 0x54, 0xce, 0x6d, 0xe7, 0x32, 0x56, - 0x18, 0xf9, 0x92, 0x8d, 0x61, 0x89, 0xf0, 0xe1, 0x8b, 0x08, 0xc2, 0x1b, 0xc0, 0x98, 0x9e, 0xe7, - 0x35, 0x7f, 0x33, 0xd0, 0x9d, 0x83, 0x48, 0x52, 0xc9, 0xfc, 0xfe, 0x53, 0xe8, 0x0c, 0x38, 0x1f, - 0xee, 0x72, 0xbf, 0xc7, 0xfa, 0x51, 0x2a, 0x1b, 0x7f, 0x8b, 0x2a, 0xaa, 0xc8, 0x2e, 0x95, 0xb4, - 0x66, 0x34, 0x8c, 0x56, 0x75, 0xfb, 0x3d, 0x3b, 0xbf, 0xab, 0x59, 0x2e, 0x3b, 0x18, 0xf6, 0x15, - 0x20, 0x6c, 0xe5, 0x6d, 0x4f, 0xb6, 0xec, 0x87, 0x9d, 0xef, 0xc0, 0x93, 0x07, 0x20, 0xa9, 0x8b, - 0x4f, 0x62, 0x6b, 0x2d, 0x89, 0x2d, 0x94, 0x63, 0x64, 0x16, 0x15, 0xb7, 0x51, 0x25, 0xcb, 0x2c, - 0x6a, 0xd7, 0x1a, 0x85, 0x56, 0x75, 0x7b, 0xcb, 0x5e, 0xf5, 0x35, 0xec, 0x8c, 0xe9, 0x16, 0x55, - 0x0a, 0x52, 0x39, 0xca, 0x02, 0x35, 0xff, 0x32, 0x50, 0xe3, 0x2a, 0x5d, 0xfb, 0x4c, 0x48, 0xfc, - 0xcd, 0x92, 0x36, 0x7b, 0x35, 0x6d, 0x8a, 0xad, 0x95, 0xdd, 0xcc, 0x94, 0x55, 0xa6, 0xc8, 0x9c, - 0xae, 0x21, 0x2a, 0x31, 0x09, 0xe3, 0xa9, 0xa8, 0xfb, 0xab, 0x8b, 0xba, 0xaa, 0x70, 0x77, 0x23, - 0x4b, 0x59, 0xda, 0x53, 0xc1, 0x49, 0x9a, 0xa3, 0xf9, 0xab, 0x81, 0x8a, 0x24, 0x1a, 0x01, 0x7e, - 0x07, 0x99, 0x34, 0x60, 0x5f, 0x84, 0x3c, 0x0a, 0x44, 0xcd, 0x68, 0x14, 0x5a, 0xa6, 0xbb, 0x91, - 0xc4, 0x96, 0xb9, 0x73, 0xb8, 0x97, 0x82, 0x24, 0xb7, 0xe3, 0x2d, 0x54, 0xa5, 0x01, 0x7b, 0x02, - 0xa1, 0x2a, 0x25, 0x2d, 0xd4, 0x74, 0x6f, 0x24, 0xb1, 0x55, 0xdd, 0x39, 0xdc, 0x9b, 0xc2, 0x64, - 0xde, 0x47, 0xc5, 0x0f, 0x41, 0xf0, 0x28, 0xf4, 0x40, 0xd4, 0x0a, 0x79, 0x7c, 0x32, 0x05, 0x49, - 0x6e, 0xc7, 0xef, 0xa2, 0x92, 0xf0, 0x78, 0x00, 0xb5, 0x62, 0xc3, 0x68, 0x99, 0xee, 0x1b, 0xaa, - 0xec, 0xb6, 0x02, 0xce, 0x62, 0xcb, 0xd4, 0x1f, 0x8f, 0x8e, 0x03, 0x20, 0xa9, 0x53, 0xf3, 0x67, - 0x03, 0x61, 0xa5, 0xe1, 0x29, 0x93, 0x83, 0x87, 0x01, 0xa4, 0x7a, 0x05, 0xfe, 0x14, 0x21, 0x3e, - 0x3b, 0x65, 0x92, 0x2c, 0xdd, 0x4d, 0x33, 0xf4, 0x2c, 0xb6, 0x36, 0x66, 0x27, 0x1d, 0x72, 0x8e, - 0x82, 0x0f, 0x51, 0x31, 0x8c, 0x46, 0x50, 0xbb, 0xb6, 0xf4, 0xc4, 0x2f, 0x78, 0x07, 0x55, 0x8c, - 0xbb, 0x9e, 0xdd, 0xb7, 0xbe, 0x5e, 0xa2, 0x23, 0x35, 0x7f, 0x32, 0xd0, 0xcd, 0x36, 0x84, 0x13, - 0xe6, 0x01, 0x81, 0x1e, 0x84, 0xe0, 0x7b, 0x80, 0x1d, 0x64, 0xfa, 0x74, 0x0c, 0x22, 0xa0, 0x1e, - 0xe8, 0x76, 0x32, 0xdd, 0xcd, 0x8c, 0x6b, 0x3e, 0x98, 0x1a, 0x48, 0xee, 0x83, 0x1b, 0xa8, 0xa8, - 0x0e, 0xba, 0x2e, 0x33, 0xcf, 0xa3, 0x7c, 0x89, 0xb6, 0xe0, 0x3b, 0xa8, 0x18, 0x50, 0x39, 0xa8, - 0x15, 0xb4, 0x47, 0x45, 0x59, 0x0f, 0xa9, 0x1c, 0x10, 0x8d, 0x6a, 0x2b, 0x0f, 0xa5, 0xbe, 0xdc, - 0x52, 0x66, 0xe5, 0xa1, 0x24, 0x1a, 0x6d, 0xfe, 0x6e, 0xa0, 0xfa, 0x13, 0x3a, 0x62, 0xdd, 0xff, - 0xdd, 0x6c, 0xff, 0x63, 0xa0, 0xe6, 0xd5, 0xca, 0xfe, 0x83, 0xe9, 0x1e, 0x2f, 0x4e, 0xf7, 0x97, - 0xab, 0xcb, 0xba, 0xba, 0xf4, 0x4b, 0xe6, 0xfb, 0xef, 0x12, 0x2a, 0x67, 0xee, 0xb3, 0xbe, 0x31, - 0x2e, 0xed, 0x9b, 0x23, 0xb4, 0xee, 0x8d, 0x18, 0xf8, 0x32, 0x0d, 0x9d, 0x75, 0xfe, 0x27, 0x2f, - 0x7d, 0xf5, 0xbb, 0x73, 0x41, 0xdc, 0xd7, 0xb2, 0x44, 0xeb, 0xf3, 0x28, 0x59, 0x48, 0x84, 0x29, - 0x2a, 0xa9, 0x01, 0x49, 0x37, 0x43, 0x75, 0xfb, 0xe3, 0x97, 0x9b, 0xb5, 0xc5, 0xc1, 0xcf, 0x6f, - 0x42, 0xd9, 0x04, 0x49, 0x23, 0xe3, 0x7d, 0xb4, 0xd1, 0xa3, 0x6c, 0x14, 0x85, 0x70, 0xc8, 0x47, - 0xcc, 0x3b, 0xce, 0x76, 0xcb, 0xdb, 0x49, 0x6c, 0x6d, 0xdc, 0x9f, 0x37, 0x9c, 0xc5, 0xd6, 0xe6, - 0x02, 0xa0, 0x17, 0xc3, 0x22, 0x19, 0x7f, 0x8f, 0x36, 0x67, 0x03, 0xd9, 0x86, 0x11, 0x78, 0x92, - 0x87, 0xb5, 0x92, 0xbe, 0xae, 0xf7, 0x57, 0xec, 0x16, 0xda, 0x81, 0xd1, 0x94, 0xea, 0xbe, 0x9e, - 0xc4, 0xd6, 0xe6, 0x83, 0xf3, 0x11, 0xc9, 0x72, 0x12, 0xfc, 0x19, 0xaa, 0x0a, 0xd6, 0x85, 0xcf, - 0x7b, 0x3d, 0xf0, 0xa4, 0xa8, 0xbd, 0xa2, 0x55, 0x34, 0xd5, 0xee, 0x6d, 0xe7, 0xf0, 0x59, 0x6c, - 0xdd, 0xc8, 0x8f, 0xbb, 0x23, 0x2a, 0x04, 0x99, 0xa7, 0xe1, 0x7b, 0xe8, 0xba, 0xfa, 0xbd, 0xf3, - 0x48, 0xb6, 0xc1, 0xe3, 0x7e, 0x57, 0xd4, 0xca, 0x7a, 0x1b, 0xe0, 0x24, 0xb6, 0xae, 0x3f, 0x5a, - 0xb0, 0x90, 0x73, 0x9e, 0xf8, 0x31, 0xba, 0x35, 0x7b, 0x13, 0x02, 0x13, 0x06, 0x47, 0xb3, 0x3f, - 0x41, 0x45, 0x6f, 0xd9, 0xb7, 0x92, 0xd8, 0xba, 0xb5, 0x73, 0xb1, 0x0b, 0xb9, 0x8c, 0xab, 0x84, - 0x8d, 0xa9, 0xf4, 0x06, 0xd9, 0xf3, 0x98, 0xb9, 0xb0, 0x83, 0x1c, 0x56, 0xc2, 0xe6, 0x8e, 0xfa, - 0x69, 0xe6, 0x69, 0xcd, 0x5f, 0x0c, 0xf4, 0xea, 0x05, 0x5d, 0x88, 0x29, 0x2a, 0x8b, 0x74, 0xf3, - 0x66, 0x43, 0x7d, 0x6f, 0xf5, 0x1e, 0x3b, 0xbf, 0xb2, 0xdd, 0x6a, 0x12, 0x5b, 0xe5, 0x29, 0x3a, - 0x8d, 0x8b, 0x5b, 0xa8, 0xe2, 0x51, 0x37, 0xf2, 0xbb, 0xd9, 0x3f, 0x63, 0xdd, 0x5d, 0x57, 0x4b, - 0x60, 0x77, 0x27, 0xc5, 0xc8, 0xcc, 0x8a, 0xdf, 0x44, 0x85, 0x28, 0x1c, 0x65, 0xeb, 0xb9, 0x9c, - 0xc4, 0x56, 0xe1, 0x31, 0xd9, 0x27, 0x0a, 0x73, 0xef, 0x9e, 0x9c, 0xd6, 0xd7, 0x9e, 0x9d, 0xd6, - 0xd7, 0x9e, 0x9f, 0xd6, 0xd7, 0x7e, 0x48, 0xea, 0xc6, 0x49, 0x52, 0x37, 0x9e, 0x25, 0x75, 0xe3, - 0x79, 0x52, 0x37, 0xfe, 0x48, 0xea, 0xc6, 0x8f, 0x7f, 0xd6, 0xd7, 0xbe, 0x2e, 0x67, 0xa5, 0xfd, - 0x1b, 0x00, 0x00, 0xff, 0xff, 0xb0, 0x64, 0x23, 0xd6, 0xd3, 0x0a, 0x00, 0x00, + // 1113 bytes of a gzipped FileDescriptorProto + 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xec, 0x55, 0x4d, 0x6f, 0x1b, 0xc5, + 0x1b, 0xcf, 0xc6, 0x76, 0x6d, 0x8f, 0x93, 0xa6, 0x99, 0xff, 0x9f, 0xd6, 0x84, 0xca, 0x6b, 0xf9, + 0x80, 0x2c, 0x41, 0x77, 0x9b, 0x80, 0x10, 0x14, 0x10, 0xca, 0x06, 0x0a, 0x91, 0x92, 0x36, 0x4c, + 0xfa, 0x22, 0xf1, 0x22, 0x75, 0xbc, 0x1e, 0xdb, 0x83, 0xed, 0x9d, 0xd5, 0xce, 0xac, 0x43, 0x6e, + 0x7c, 0x04, 0xbe, 0x02, 0x27, 0x3e, 0x05, 0x07, 0x6e, 0xe1, 0xd6, 0x63, 0x2f, 0xac, 0xc8, 0x72, + 0xe2, 0xc0, 0x81, 0x6b, 0x4e, 0x68, 0x66, 0xc7, 0xeb, 0x97, 0x4d, 0x8a, 0x29, 0xa2, 0x17, 0x7a, + 0xdb, 0xf9, 0x3d, 0xf3, 0xfc, 0x9e, 0x97, 0xd9, 0xe7, 0xf9, 0x81, 0x4f, 0xfb, 0x6f, 0x73, 0x8b, + 0x32, 0xbb, 0x1f, 0xb6, 0x48, 0xe0, 0x11, 0x41, 0xb8, 0x3d, 0x22, 0x5e, 0x9b, 0x05, 0xb6, 0x36, + 0x60, 0x9f, 0xda, 0xb8, 0x3d, 0xa4, 0x9c, 0x53, 0xe6, 0x05, 0xa4, 0x4b, 0xb9, 0x08, 0xb0, 0xa0, + 0xcc, 0xb3, 0x47, 0x9b, 0x2d, 0x22, 0xf0, 0xa6, 0xdd, 0x25, 0x1e, 0x09, 0xb0, 0x20, 0x6d, 0xcb, + 0x0f, 0x98, 0x60, 0xb0, 0x99, 0x78, 0x5a, 0xd8, 0xa7, 0xd6, 0xb9, 0x9e, 0x96, 0xf6, 0xdc, 0xb8, + 0xd1, 0xa5, 0xa2, 0x17, 0xb6, 0x2c, 0x97, 0x0d, 0xed, 0x2e, 0xeb, 0x32, 0x5b, 0x11, 0xb4, 0xc2, + 0x8e, 0x3a, 0xa9, 0x83, 0xfa, 0x4a, 0x88, 0x37, 0xde, 0x9c, 0xa4, 0x34, 0xc4, 0x6e, 0x8f, 0x7a, + 0x24, 0x38, 0xb6, 0xfd, 0x7e, 0x57, 0x02, 0xdc, 0x1e, 0x12, 0x81, 0xed, 0x51, 0x26, 0x9d, 0x0d, + 0xfb, 0x22, 0xaf, 0x20, 0xf4, 0x04, 0x1d, 0x92, 0x8c, 0xc3, 0x5b, 0x7f, 0xe5, 0xc0, 0xdd, 0x1e, + 0x19, 0xe2, 0x79, 0xbf, 0xc6, 0x4f, 0x45, 0xb0, 0xb6, 0x1f, 0x0a, 0x2c, 0xa8, 0xd7, 0x7d, 0x48, + 0x5a, 0x3d, 0xc6, 0xfa, 0xb0, 0x0e, 0xf2, 0x1e, 0x1e, 0x92, 0xaa, 0x51, 0x37, 0x9a, 0x65, 0x67, + 0xe5, 0x24, 0x32, 0x97, 0xe2, 0xc8, 0xcc, 0xdf, 0xc1, 0x43, 0x82, 0x94, 0x05, 0x1e, 0x81, 0x15, + 0x77, 0x40, 0x89, 0x27, 0x76, 0x98, 0xd7, 0xa1, 0xdd, 0xea, 0x72, 0xdd, 0x68, 0x56, 0xb6, 0xde, + 0xb7, 0x16, 0x6d, 0xa2, 0xa5, 0x43, 0xed, 0x4c, 0x91, 0x38, 0xff, 0xd7, 0x81, 0x56, 0xa6, 0x51, + 0x34, 0x13, 0x08, 0x62, 0x50, 0x08, 0xc2, 0x01, 0xe1, 0xd5, 0x5c, 0x3d, 0xd7, 0xac, 0x6c, 0xbd, + 0xb7, 0x78, 0x44, 0x14, 0x0e, 0xc8, 0x43, 0x2a, 0x7a, 0x77, 0x7d, 0x92, 0x58, 0xb8, 0xb3, 0xaa, + 0x03, 0x16, 0xa4, 0x8d, 0xa3, 0x84, 0x19, 0xee, 0x81, 0xd5, 0x0e, 0xa6, 0x83, 0x30, 0x20, 0x07, + 0x6c, 0x40, 0xdd, 0xe3, 0x6a, 0x5e, 0xb5, 0xe1, 0xd5, 0x38, 0x32, 0x57, 0x6f, 0x4f, 0x1b, 0xce, + 0x22, 0x73, 0x7d, 0x06, 0xb8, 0x77, 0xec, 0x13, 0x34, 0xeb, 0x0c, 0xbf, 0x06, 0xeb, 0xb2, 0x63, + 0xdc, 0xc7, 0x2e, 0x39, 0x24, 0x03, 0xe2, 0x0a, 0x16, 0x54, 0x0b, 0xaa, 0x5d, 0x6f, 0x4c, 0x25, + 0x9f, 0xbe, 0x99, 0xe5, 0xf7, 0xbb, 0x12, 0xe0, 0x96, 0xfc, 0x35, 0xac, 0xd1, 0xa6, 0xb5, 0x87, + 0x5b, 0x64, 0x30, 0x76, 0x75, 0x5e, 0x8a, 0x23, 0x73, 0xfd, 0xce, 0x3c, 0x23, 0xca, 0x06, 0x81, + 0x1f, 0x82, 0x0a, 0xa7, 0x6d, 0xf2, 0x51, 0xa7, 0x43, 0x5c, 0xc1, 0xab, 0x97, 0x54, 0x15, 0x8d, + 0x38, 0x32, 0x2b, 0x87, 0x13, 0xf8, 0x2c, 0x32, 0xd7, 0x26, 0xc7, 0x9d, 0x01, 0xe6, 0x1c, 0x4d, + 0xbb, 0xc1, 0x5b, 0xe0, 0xb2, 0xfc, 0x7d, 0x58, 0x28, 0x0e, 0x89, 0xcb, 0xbc, 0x36, 0xaf, 0x16, + 0xeb, 0x46, 0xb3, 0xe0, 0xc0, 0x38, 0x32, 0x2f, 0xdf, 0x9b, 0xb1, 0xa0, 0xb9, 0x9b, 0xf0, 0x3e, + 0xb8, 0x96, 0xbe, 0x09, 0x22, 0x23, 0x4a, 0x8e, 0x1e, 0x90, 0x40, 0x1e, 0x78, 0xb5, 0x54, 0xcf, + 0x35, 0xcb, 0xce, 0x2b, 0x71, 0x64, 0x5e, 0xdb, 0x3e, 0xff, 0x0a, 0xba, 0xc8, 0x57, 0x16, 0x36, + 0xc4, 0xc2, 0xed, 0xe9, 0xe7, 0x29, 0x4f, 0x0a, 0xdb, 0x9f, 0xc0, 0xb2, 0xb0, 0xa9, 0xa3, 0x7a, + 0x9a, 0x69, 0x37, 0xf8, 0x08, 0xc0, 0x80, 0x50, 0x6f, 0xc4, 0x5c, 0xf5, 0x37, 0x68, 0x32, 0xa0, + 0xc8, 0x6e, 0xc6, 0x91, 0x09, 0x51, 0xc6, 0x7a, 0x16, 0x99, 0x57, 0xb3, 0xa8, 0xa2, 0x3e, 0x87, + 0x0b, 0x32, 0x70, 0x99, 0xb5, 0xbe, 0x22, 0xae, 0x48, 0xdf, 0xbd, 0xf2, 0xec, 0xef, 0xae, 0xfa, + 0x7d, 0x77, 0x86, 0x0e, 0xcd, 0xd1, 0x37, 0x7e, 0x36, 0xc0, 0xf5, 0xb9, 0x59, 0x4e, 0xc6, 0x26, + 0x4c, 0xfe, 0x78, 0xf8, 0x08, 0x94, 0x24, 0x7b, 0x1b, 0x0b, 0xac, 0x86, 0xbb, 0xb2, 0x75, 0x73, + 0xb1, 0x5c, 0x92, 0xc0, 0xfb, 0x44, 0x60, 0x07, 0xea, 0xa1, 0x01, 0x13, 0x0c, 0xa5, 0xac, 0xf0, + 0x73, 0x50, 0xd2, 0x91, 0x79, 0x75, 0x59, 0x8d, 0xe8, 0x3b, 0x8b, 0x8f, 0xe8, 0x5c, 0xee, 0x4e, + 0x5e, 0x86, 0x42, 0xa5, 0x23, 0x4d, 0xd8, 0xf8, 0xdd, 0x00, 0xf5, 0xa7, 0xd5, 0xb7, 0x47, 0xb9, + 0x80, 0x5f, 0x64, 0x6a, 0xb4, 0x16, 0xec, 0x37, 0xe5, 0x49, 0x85, 0x57, 0x74, 0x85, 0xa5, 0x31, + 0x32, 0x55, 0x5f, 0x1f, 0x14, 0xa8, 0x20, 0xc3, 0x71, 0x71, 0xb7, 0x9f, 0xb9, 0xb8, 0x99, 0xc4, + 0x27, 0x9b, 0x68, 0x57, 0x92, 0xa3, 0x24, 0x46, 0xe3, 0x47, 0x03, 0xe4, 0xe5, 0x6a, 0x82, 0xaf, + 0x81, 0x32, 0xf6, 0xe9, 0xc7, 0x01, 0x0b, 0x7d, 0x5e, 0x35, 0xd4, 0xe8, 0xac, 0xc6, 0x91, 0x59, + 0xde, 0x3e, 0xd8, 0x4d, 0x40, 0x34, 0xb1, 0xc3, 0x4d, 0x50, 0xc1, 0x3e, 0x4d, 0x27, 0x6d, 0x59, + 0x5d, 0x5f, 0x93, 0xe3, 0xb1, 0x7d, 0xb0, 0x9b, 0x4e, 0xd7, 0xf4, 0x1d, 0xc9, 0x1f, 0x10, 0xce, + 0xc2, 0xc0, 0xd5, 0x9b, 0x55, 0xf3, 0xa3, 0x31, 0x88, 0x26, 0x76, 0xf8, 0x3a, 0x28, 0x70, 0x97, + 0xf9, 0x44, 0xef, 0xc5, 0xab, 0x32, 0xed, 0x43, 0x09, 0x9c, 0x45, 0x66, 0x59, 0x7d, 0xa8, 0x89, + 0x48, 0x2e, 0x35, 0xbe, 0x37, 0x00, 0xcc, 0xae, 0x5e, 0xf8, 0x01, 0x00, 0x2c, 0x3d, 0xe9, 0x92, + 0x4c, 0xf5, 0x57, 0xa5, 0xe8, 0x59, 0x64, 0xae, 0xa6, 0x27, 0x45, 0x39, 0xe5, 0x02, 0x0f, 0x40, + 0x5e, 0xae, 0x6b, 0xad, 0x3c, 0xd6, 0xdf, 0xd3, 0x81, 0x89, 0xa6, 0xc9, 0x13, 0x52, 0x4c, 0x8d, + 0xef, 0x0c, 0x70, 0xe5, 0x90, 0x04, 0x23, 0xea, 0x12, 0x44, 0x3a, 0x24, 0x20, 0x9e, 0x4b, 0xa0, + 0x0d, 0xca, 0xe9, 0x66, 0xd5, 0x7a, 0xb8, 0xae, 0x7d, 0xcb, 0xe9, 0x16, 0x46, 0x93, 0x3b, 0xa9, + 0x76, 0x2e, 0x5f, 0xa8, 0x9d, 0xd7, 0x41, 0xde, 0xc7, 0xa2, 0x57, 0xcd, 0xa9, 0x1b, 0x25, 0x69, + 0x3d, 0xc0, 0xa2, 0x87, 0x14, 0xaa, 0xac, 0x2c, 0x10, 0xaa, 0xb9, 0x05, 0x6d, 0x65, 0x81, 0x40, + 0x0a, 0x6d, 0xfc, 0x76, 0x09, 0xac, 0x3f, 0xc0, 0x03, 0xda, 0x7e, 0xa1, 0xd7, 0x2f, 0xf4, 0xfa, + 0xbf, 0xa5, 0xd7, 0x59, 0x35, 0x05, 0xff, 0xae, 0x9a, 0x9e, 0x1a, 0xa0, 0x96, 0x99, 0xb5, 0xe7, + 0xad, 0xa7, 0x5f, 0x66, 0xf4, 0xf4, 0xdd, 0xc5, 0x47, 0x28, 0x93, 0x7d, 0x46, 0x51, 0xff, 0x30, + 0x40, 0xe3, 0xe9, 0x35, 0x3e, 0x07, 0x4d, 0x1d, 0xce, 0x6a, 0xea, 0x27, 0xff, 0xa0, 0xc0, 0x45, + 0x54, 0xf5, 0x07, 0x03, 0xfc, 0xef, 0x9c, 0x75, 0x06, 0x31, 0x28, 0xf2, 0x64, 0xfd, 0xeb, 0x1a, + 0x6f, 0x2d, 0x9e, 0xc8, 0xbc, 0x6e, 0x38, 0x95, 0x38, 0x32, 0x8b, 0x63, 0x74, 0xcc, 0x0b, 0x9b, + 0xa0, 0xe4, 0x62, 0x27, 0xf4, 0xda, 0x5a, 0xb8, 0x56, 0x9c, 0x15, 0xd9, 0x93, 0x9d, 0xed, 0x04, + 0x43, 0xa9, 0x15, 0xbe, 0x0c, 0x72, 0x61, 0x30, 0xd0, 0x1a, 0x51, 0x8c, 0x23, 0x33, 0x77, 0x1f, + 0xed, 0x21, 0x89, 0x39, 0x37, 0x4e, 0x4e, 0x6b, 0x4b, 0x8f, 0x4f, 0x6b, 0x4b, 0x4f, 0x4e, 0x6b, + 0x4b, 0xdf, 0xc4, 0x35, 0xe3, 0x24, 0xae, 0x19, 0x8f, 0xe3, 0x9a, 0xf1, 0x24, 0xae, 0x19, 0xbf, + 0xc4, 0x35, 0xe3, 0xdb, 0x5f, 0x6b, 0x4b, 0x9f, 0x15, 0x75, 0x6a, 0x7f, 0x06, 0x00, 0x00, 0xff, + 0xff, 0xc3, 0x6f, 0x8b, 0x7e, 0x2c, 0x0f, 0x00, 0x00, } diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto index c3b3c58638e..c133192647d 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/generated.proto @@ -28,6 +28,156 @@ import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto"; // Package-wide variables from generator "generated". option go_package = "v1beta1"; +// MutatingWebhook describes an admission webhook and the resources and operations it applies to. +message MutatingWebhook { + // 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. + optional string name = 1; + + // ClientConfig defines how to communicate with the hook. + // Required + optional WebhookClientConfig clientConfig = 2; + + // Rules describes what operations on what resources/subresources the webhook cares about. + // The webhook cares about an operation if it matches _any_ Rule. + // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks + // from putting the cluster in a state which cannot be recovered from without completely + // disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called + // on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects. + repeated RuleWithOperations rules = 3; + + // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // allowed values are Ignore or Fail. Defaults to Ignore. + // +optional + optional string failurePolicy = 4; + + // matchPolicy defines how the "rules" list is used to match incoming requests. + // Allowed values are "Exact" or "Equivalent". + // + // - 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. + // + // - 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. + // + // Defaults to "Exact" + // +optional + optional string matchPolicy = 9; + + // NamespaceSelector decides whether to run the webhook on an object based + // on whether the namespace for that object matches the selector. If the + // object itself is a namespace, the matching is performed on + // object.metadata.labels. If the object is another cluster scoped resource, + // it never skips the webhook. + // + // For example, to run the webhook on any objects whose namespace is not + // associated with "runlevel" of "0" or "1"; you will set the selector as + // follows: + // "namespaceSelector": { + // "matchExpressions": [ + // { + // "key": "runlevel", + // "operator": "NotIn", + // "values": [ + // "0", + // "1" + // ] + // } + // ] + // } + // + // If instead you want to only run the webhook on any objects whose + // namespace is associated with the "environment" of "prod" or "staging"; + // you will set the selector as follows: + // "namespaceSelector": { + // "matchExpressions": [ + // { + // "key": "environment", + // "operator": "In", + // "values": [ + // "prod", + // "staging" + // ] + // } + // ] + // } + // + // See + // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // for more examples of label selectors. + // + // Default to the empty LabelSelector, which matches everything. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 5; + + // ObjectSelector decides whether to run the webhook based on if the + // object has matching labels. objectSelector is evaluated against both + // the oldObject and newObject that would be sent to the webhook, and + // is considered to match if either object matches the selector. A null + // object (oldObject in the case of create, or newObject in the case of + // delete) or an object that cannot have labels (like a + // DeploymentRollback or a PodProxyOptions object) is not considered to + // match. + // Use the object selector only if the webhook is opt-in, because end + // users may skip the admission webhook by setting the labels. + // Default to the empty LabelSelector, which matches everything. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 11; + + // SideEffects states whether this webhookk 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 change 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. + // +optional + optional string sideEffects = 6; + + // 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. + // +optional + optional int32 timeoutSeconds = 7; + + // 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']`. + // +optional + repeated string admissionReviewVersions = 8; + + // reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. + // Allowed values are "Never" and "IfNeeded". + // + // Never: the webhook will not be called more than once in a single admission evaluation. + // + // IfNeeded: 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. + // + // Defaults to "Never". + // +optional + optional string reinvocationPolicy = 10; +} + // MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object. message MutatingWebhookConfiguration { // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. @@ -38,7 +188,7 @@ message MutatingWebhookConfiguration { // +optional // +patchMergeKey=name // +patchStrategy=merge - repeated Webhook Webhooks = 2; + repeated MutatingWebhook Webhooks = 2; } // MutatingWebhookConfigurationList is a list of MutatingWebhookConfiguration. @@ -131,32 +281,8 @@ message ServiceReference { optional int32 port = 4; } -// ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it. -message ValidatingWebhookConfiguration { - // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; - - // Webhooks is a list of webhooks and the affected resources and operations. - // +optional - // +patchMergeKey=name - // +patchStrategy=merge - repeated Webhook Webhooks = 2; -} - -// ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. -message ValidatingWebhookConfigurationList { - // Standard list metadata. - // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds - // +optional - optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; - - // List of ValidatingWebhookConfiguration. - repeated ValidatingWebhookConfiguration items = 2; -} - -// Webhook describes an admission webhook and the resources and operations it applies to. -message Webhook { +// ValidatingWebhook describes an admission webhook and the resources and operations it applies to. +message ValidatingWebhook { // 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 @@ -237,13 +363,27 @@ message Webhook { // } // // See - // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ + // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels // for more examples of label selectors. // // Default to the empty LabelSelector, which matches everything. // +optional optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector namespaceSelector = 5; + // ObjectSelector decides whether to run the webhook based on if the + // object has matching labels. objectSelector is evaluated against both + // the oldObject and newObject that would be sent to the webhook, and + // is considered to match if either object matches the selector. A null + // object (oldObject in the case of create, or newObject in the case of + // delete) or an object that cannot have labels (like a + // DeploymentRollback or a PodProxyOptions object) is not considered to + // match. + // Use the object selector only if the webhook is opt-in, because end + // users may skip the admission webhook by setting the labels. + // Default to the empty LabelSelector, which matches everything. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.LabelSelector objectSelector = 10; + // SideEffects states whether this webhookk has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be @@ -273,6 +413,30 @@ message Webhook { repeated string admissionReviewVersions = 8; } +// ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it. +message ValidatingWebhookConfiguration { + // Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata. + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ObjectMeta metadata = 1; + + // Webhooks is a list of webhooks and the affected resources and operations. + // +optional + // +patchMergeKey=name + // +patchStrategy=merge + repeated ValidatingWebhook Webhooks = 2; +} + +// ValidatingWebhookConfigurationList is a list of ValidatingWebhookConfiguration. +message ValidatingWebhookConfigurationList { + // Standard list metadata. + // More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#types-kinds + // +optional + optional k8s.io.apimachinery.pkg.apis.meta.v1.ListMeta metadata = 1; + + // List of ValidatingWebhookConfiguration. + repeated ValidatingWebhookConfiguration items = 2; +} + // WebhookClientConfig contains the information to make a TLS // connection with the webhook message WebhookClientConfig { diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go index d3a034d2e3f..6b8c5a23a72 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/types.go @@ -124,7 +124,7 @@ type ValidatingWebhookConfiguration struct { // +optional // +patchMergeKey=name // +patchStrategy=merge - Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"` + Webhooks []ValidatingWebhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -154,7 +154,7 @@ type MutatingWebhookConfiguration struct { // +optional // +patchMergeKey=name // +patchStrategy=merge - Webhooks []Webhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"` + Webhooks []MutatingWebhook `json:"webhooks,omitempty" patchStrategy:"merge" patchMergeKey:"name" protobuf:"bytes,2,rep,name=Webhooks"` } // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object @@ -170,8 +170,140 @@ type MutatingWebhookConfigurationList struct { Items []MutatingWebhookConfiguration `json:"items" protobuf:"bytes,2,rep,name=items"` } -// Webhook describes an admission webhook and the resources and operations it applies to. -type Webhook struct { +// ValidatingWebhook describes an admission webhook and the resources and operations it applies to. +type ValidatingWebhook struct { + // 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. + Name string `json:"name" protobuf:"bytes,1,opt,name=name"` + + // ClientConfig defines how to communicate with the hook. + // Required + ClientConfig WebhookClientConfig `json:"clientConfig" protobuf:"bytes,2,opt,name=clientConfig"` + + // Rules describes what operations on what resources/subresources the webhook cares about. + // The webhook cares about an operation if it matches _any_ Rule. + // However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks + // from putting the cluster in a state which cannot be recovered from without completely + // disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called + // on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects. + Rules []RuleWithOperations `json:"rules,omitempty" protobuf:"bytes,3,rep,name=rules"` + + // FailurePolicy defines how unrecognized errors from the admission endpoint are handled - + // allowed values are Ignore or Fail. Defaults to Ignore. + // +optional + FailurePolicy *FailurePolicyType `json:"failurePolicy,omitempty" protobuf:"bytes,4,opt,name=failurePolicy,casttype=FailurePolicyType"` + + // matchPolicy defines how the "rules" list is used to match incoming requests. + // Allowed values are "Exact" or "Equivalent". + // + // - 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. + // + // - 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. + // + // Defaults to "Exact" + // +optional + MatchPolicy *MatchPolicyType `json:"matchPolicy,omitempty" protobuf:"bytes,9,opt,name=matchPolicy,casttype=MatchPolicyType"` + + // NamespaceSelector decides whether to run the webhook on an object based + // on whether the namespace for that object matches the selector. If the + // object itself is a namespace, the matching is performed on + // object.metadata.labels. If the object is another cluster scoped resource, + // it never skips the webhook. + // + // For example, to run the webhook on any objects whose namespace is not + // associated with "runlevel" of "0" or "1"; you will set the selector as + // follows: + // "namespaceSelector": { + // "matchExpressions": [ + // { + // "key": "runlevel", + // "operator": "NotIn", + // "values": [ + // "0", + // "1" + // ] + // } + // ] + // } + // + // If instead you want to only run the webhook on any objects whose + // namespace is associated with the "environment" of "prod" or "staging"; + // you will set the selector as follows: + // "namespaceSelector": { + // "matchExpressions": [ + // { + // "key": "environment", + // "operator": "In", + // "values": [ + // "prod", + // "staging" + // ] + // } + // ] + // } + // + // See + // https://kubernetes.io/docs/concepts/overview/working-with-objects/labels + // for more examples of label selectors. + // + // Default to the empty LabelSelector, which matches everything. + // +optional + NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"` + + // ObjectSelector decides whether to run the webhook based on if the + // object has matching labels. objectSelector is evaluated against both + // the oldObject and newObject that would be sent to the webhook, and + // is considered to match if either object matches the selector. A null + // object (oldObject in the case of create, or newObject in the case of + // delete) or an object that cannot have labels (like a + // DeploymentRollback or a PodProxyOptions object) is not considered to + // match. + // Use the object selector only if the webhook is opt-in, because end + // users may skip the admission webhook by setting the labels. + // Default to the empty LabelSelector, which matches everything. + // +optional + ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,10,opt,name=objectSelector"` + + // SideEffects states whether this webhookk 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 change 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. + // +optional + SideEffects *SideEffectClass `json:"sideEffects,omitempty" protobuf:"bytes,6,opt,name=sideEffects,casttype=SideEffectClass"` + + // 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. + // +optional + TimeoutSeconds *int32 `json:"timeoutSeconds,omitempty" protobuf:"varint,7,opt,name=timeoutSeconds"` + + // 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']`. + // +optional + AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty" protobuf:"bytes,8,rep,name=admissionReviewVersions"` +} + +// MutatingWebhook describes an admission webhook and the resources and operations it applies to. +type MutatingWebhook struct { // 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 @@ -259,6 +391,20 @@ type Webhook struct { // +optional NamespaceSelector *metav1.LabelSelector `json:"namespaceSelector,omitempty" protobuf:"bytes,5,opt,name=namespaceSelector"` + // ObjectSelector decides whether to run the webhook based on if the + // object has matching labels. objectSelector is evaluated against both + // the oldObject and newObject that would be sent to the webhook, and + // is considered to match if either object matches the selector. A null + // object (oldObject in the case of create, or newObject in the case of + // delete) or an object that cannot have labels (like a + // DeploymentRollback or a PodProxyOptions object) is not considered to + // match. + // Use the object selector only if the webhook is opt-in, because end + // users may skip the admission webhook by setting the labels. + // Default to the empty LabelSelector, which matches everything. + // +optional + ObjectSelector *metav1.LabelSelector `json:"objectSelector,omitempty" protobuf:"bytes,11,opt,name=objectSelector"` + // SideEffects states whether this webhookk has side effects. // Acceptable values are: Unknown, None, Some, NoneOnDryRun // Webhooks with side effects MUST implement a reconciliation system, since a request may be @@ -286,8 +432,39 @@ type Webhook struct { // Default to `['v1beta1']`. // +optional AdmissionReviewVersions []string `json:"admissionReviewVersions,omitempty" protobuf:"bytes,8,rep,name=admissionReviewVersions"` + + // reinvocationPolicy indicates whether this webhook should be called multiple times as part of a single admission evaluation. + // Allowed values are "Never" and "IfNeeded". + // + // Never: the webhook will not be called more than once in a single admission evaluation. + // + // IfNeeded: 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. + // + // Defaults to "Never". + // +optional + ReinvocationPolicy *ReinvocationPolicyType `json:"reinvocationPolicy,omitempty" protobuf:"bytes,10,opt,name=reinvocationPolicy,casttype=ReinvocationPolicyType"` } +// ReinvocationPolicyType specifies what type of policy the admission hook uses. +type ReinvocationPolicyType string + +const ( + // NeverReinvocationPolicy indicates that the webhook must not be called more than once in a + // single admission evaluation. + NeverReinvocationPolicy ReinvocationPolicyType = "Never" + // IfNeededReinvocationPolicy indicates that the webhook may 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. + IfNeededReinvocationPolicy ReinvocationPolicyType = "IfNeeded" +) + // RuleWithOperations is a tuple of Operations and Resources. It is recommended to make // sure that all the tuple expansions are valid. type RuleWithOperations struct { diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go index 43ea31306b2..39e86db9769 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/types_swagger_doc_generated.go @@ -27,6 +27,25 @@ package v1beta1 // Those methods can be generated by using hack/update-generated-swagger-docs.sh // AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT. +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.", + "clientConfig": "ClientConfig defines how to communicate with the hook. Required", + "rules": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + "matchPolicy": "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 \"Exact\"", + "namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "sideEffects": "SideEffects states whether this webhookk 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 change 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']`.", + "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\".", +} + +func (MutatingWebhook) SwaggerDoc() map[string]string { + return map_MutatingWebhook +} + var map_MutatingWebhookConfiguration = map[string]string{ "": "MutatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and may change the object.", "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.", @@ -80,6 +99,24 @@ func (ServiceReference) SwaggerDoc() map[string]string { return map_ServiceReference } +var map_ValidatingWebhook = map[string]string{ + "": "ValidatingWebhook 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.", + "clientConfig": "ClientConfig defines how to communicate with the hook. Required", + "rules": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", + "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", + "matchPolicy": "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 \"Exact\"", + "namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", + "objectSelector": "ObjectSelector decides whether to run the webhook based on if the object has matching labels. objectSelector is evaluated against both the oldObject and newObject that would be sent to the webhook, and is considered to match if either object matches the selector. A null object (oldObject in the case of create, or newObject in the case of delete) or an object that cannot have labels (like a DeploymentRollback or a PodProxyOptions object) is not considered to match. Use the object selector only if the webhook is opt-in, because end users may skip the admission webhook by setting the labels. Default to the empty LabelSelector, which matches everything.", + "sideEffects": "SideEffects states whether this webhookk 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 change 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']`.", +} + +func (ValidatingWebhook) SwaggerDoc() map[string]string { + return map_ValidatingWebhook +} + var map_ValidatingWebhookConfiguration = map[string]string{ "": "ValidatingWebhookConfiguration describes the configuration of and admission webhook that accept or reject and object without changing it.", "metadata": "Standard object metadata; More info: https://git.k8s.io/community/contributors/devel/api-conventions.md#metadata.", @@ -100,23 +137,6 @@ func (ValidatingWebhookConfigurationList) SwaggerDoc() map[string]string { return map_ValidatingWebhookConfigurationList } -var map_Webhook = map[string]string{ - "": "Webhook 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.", - "clientConfig": "ClientConfig defines how to communicate with the hook. Required", - "rules": "Rules describes what operations on what resources/subresources the webhook cares about. The webhook cares about an operation if it matches _any_ Rule. However, in order to prevent ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks from putting the cluster in a state which cannot be recovered from without completely disabling the plugin, ValidatingAdmissionWebhooks and MutatingAdmissionWebhooks are never called on admission requests for ValidatingWebhookConfiguration and MutatingWebhookConfiguration objects.", - "failurePolicy": "FailurePolicy defines how unrecognized errors from the admission endpoint are handled - allowed values are Ignore or Fail. Defaults to Ignore.", - "matchPolicy": "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 \"Exact\"", - "namespaceSelector": "NamespaceSelector decides whether to run the webhook on an object based on whether the namespace for that object matches the selector. If the object itself is a namespace, the matching is performed on object.metadata.labels. If the object is another cluster scoped resource, it never skips the webhook.\n\nFor example, to run the webhook on any objects whose namespace is not associated with \"runlevel\" of \"0\" or \"1\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"runlevel\",\n \"operator\": \"NotIn\",\n \"values\": [\n \"0\",\n \"1\"\n ]\n }\n ]\n}\n\nIf instead you want to only run the webhook on any objects whose namespace is associated with the \"environment\" of \"prod\" or \"staging\"; you will set the selector as follows: \"namespaceSelector\": {\n \"matchExpressions\": [\n {\n \"key\": \"environment\",\n \"operator\": \"In\",\n \"values\": [\n \"prod\",\n \"staging\"\n ]\n }\n ]\n}\n\nSee https://kubernetes.io/docs/concepts/overview/working-with-objects/labels/ for more examples of label selectors.\n\nDefault to the empty LabelSelector, which matches everything.", - "sideEffects": "SideEffects states whether this webhookk 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 change 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']`.", -} - -func (Webhook) SwaggerDoc() map[string]string { - return map_Webhook -} - var map_WebhookClientConfig = map[string]string{ "": "WebhookClientConfig contains the information to make a TLS connection with the webhook", "url": "`url` gives the location of the webhook, in standard URL form (`scheme://host:port/path`). Exactly one of `url` or `service` must be specified.\n\nThe `host` should not refer to a service running in the cluster; use the `service` field instead. The host might be resolved via external DNS in some apiservers (e.g., `kube-apiserver` cannot resolve in-cluster DNS as that would be a layering violation). `host` may also be an IP address.\n\nPlease note that using `localhost` or `127.0.0.1` as a `host` is risky unless you take great care to run this webhook on all hosts which run an apiserver which might need to make calls to this webhook. Such installs are likely to be non-portable, i.e., not easy to turn up in a new cluster.\n\nThe scheme must be \"https\"; the URL must begin with \"https://\".\n\nA path is optional, and if present may be any string permissible in a URL. You may use the path to pass an arbitrary string to the webhook, for example, a cluster identifier.\n\nAttempting to use a user or basic auth e.g. \"user:password@\" is not allowed. Fragments (\"#...\") and query parameters (\"?...\") are not allowed, either.", diff --git a/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.deepcopy.go b/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.deepcopy.go index f2dea030b49..c4570d0311b 100644 --- a/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.deepcopy.go +++ b/staging/src/k8s.io/api/admissionregistration/v1beta1/zz_generated.deepcopy.go @@ -25,6 +25,70 @@ 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 *MutatingWebhook) DeepCopyInto(out *MutatingWebhook) { + *out = *in + in.ClientConfig.DeepCopyInto(&out.ClientConfig) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]RuleWithOperations, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + *out = new(FailurePolicyType) + **out = **in + } + if in.MatchPolicy != nil { + in, out := &in.MatchPolicy, &out.MatchPolicy + *out = new(MatchPolicyType) + **out = **in + } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.ObjectSelector != nil { + in, out := &in.ObjectSelector, &out.ObjectSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.SideEffects != nil { + in, out := &in.SideEffects, &out.SideEffects + *out = new(SideEffectClass) + **out = **in + } + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int32) + **out = **in + } + if in.AdmissionReviewVersions != nil { + in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + if in.ReinvocationPolicy != nil { + in, out := &in.ReinvocationPolicy, &out.ReinvocationPolicy + *out = new(ReinvocationPolicyType) + **out = **in + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new MutatingWebhook. +func (in *MutatingWebhook) DeepCopy() *MutatingWebhook { + if in == nil { + return nil + } + out := new(MutatingWebhook) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *MutatingWebhookConfiguration) DeepCopyInto(out *MutatingWebhookConfiguration) { *out = *in @@ -32,7 +96,7 @@ func (in *MutatingWebhookConfiguration) DeepCopyInto(out *MutatingWebhookConfigu in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) if in.Webhooks != nil { in, out := &in.Webhooks, &out.Webhooks - *out = make([]Webhook, len(*in)) + *out = make([]MutatingWebhook, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -175,6 +239,65 @@ func (in *ServiceReference) DeepCopy() *ServiceReference { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *ValidatingWebhook) DeepCopyInto(out *ValidatingWebhook) { + *out = *in + in.ClientConfig.DeepCopyInto(&out.ClientConfig) + if in.Rules != nil { + in, out := &in.Rules, &out.Rules + *out = make([]RuleWithOperations, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } + if in.FailurePolicy != nil { + in, out := &in.FailurePolicy, &out.FailurePolicy + *out = new(FailurePolicyType) + **out = **in + } + if in.MatchPolicy != nil { + in, out := &in.MatchPolicy, &out.MatchPolicy + *out = new(MatchPolicyType) + **out = **in + } + if in.NamespaceSelector != nil { + in, out := &in.NamespaceSelector, &out.NamespaceSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.ObjectSelector != nil { + in, out := &in.ObjectSelector, &out.ObjectSelector + *out = new(v1.LabelSelector) + (*in).DeepCopyInto(*out) + } + if in.SideEffects != nil { + in, out := &in.SideEffects, &out.SideEffects + *out = new(SideEffectClass) + **out = **in + } + if in.TimeoutSeconds != nil { + in, out := &in.TimeoutSeconds, &out.TimeoutSeconds + *out = new(int32) + **out = **in + } + if in.AdmissionReviewVersions != nil { + in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions + *out = make([]string, len(*in)) + copy(*out, *in) + } + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ValidatingWebhook. +func (in *ValidatingWebhook) DeepCopy() *ValidatingWebhook { + if in == nil { + return nil + } + out := new(ValidatingWebhook) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *ValidatingWebhookConfiguration) DeepCopyInto(out *ValidatingWebhookConfiguration) { *out = *in @@ -182,7 +305,7 @@ func (in *ValidatingWebhookConfiguration) DeepCopyInto(out *ValidatingWebhookCon in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) if in.Webhooks != nil { in, out := &in.Webhooks, &out.Webhooks - *out = make([]Webhook, len(*in)) + *out = make([]ValidatingWebhook, len(*in)) for i := range *in { (*in)[i].DeepCopyInto(&(*out)[i]) } @@ -241,60 +364,6 @@ func (in *ValidatingWebhookConfigurationList) DeepCopyObject() runtime.Object { return nil } -// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. -func (in *Webhook) DeepCopyInto(out *Webhook) { - *out = *in - in.ClientConfig.DeepCopyInto(&out.ClientConfig) - if in.Rules != nil { - in, out := &in.Rules, &out.Rules - *out = make([]RuleWithOperations, len(*in)) - for i := range *in { - (*in)[i].DeepCopyInto(&(*out)[i]) - } - } - if in.FailurePolicy != nil { - in, out := &in.FailurePolicy, &out.FailurePolicy - *out = new(FailurePolicyType) - **out = **in - } - if in.MatchPolicy != nil { - in, out := &in.MatchPolicy, &out.MatchPolicy - *out = new(MatchPolicyType) - **out = **in - } - if in.NamespaceSelector != nil { - in, out := &in.NamespaceSelector, &out.NamespaceSelector - *out = new(v1.LabelSelector) - (*in).DeepCopyInto(*out) - } - if in.SideEffects != nil { - in, out := &in.SideEffects, &out.SideEffects - *out = new(SideEffectClass) - **out = **in - } - if in.TimeoutSeconds != nil { - in, out := &in.TimeoutSeconds, &out.TimeoutSeconds - *out = new(int32) - **out = **in - } - if in.AdmissionReviewVersions != nil { - in, out := &in.AdmissionReviewVersions, &out.AdmissionReviewVersions - *out = make([]string, len(*in)) - copy(*out, *in) - } - return -} - -// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new Webhook. -func (in *Webhook) DeepCopy() *Webhook { - if in == nil { - return nil - } - out := new(Webhook) - in.DeepCopyInto(out) - return out -} - // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *WebhookClientConfig) DeepCopyInto(out *WebhookClientConfig) { *out = *in diff --git a/staging/src/k8s.io/apiserver/pkg/admission/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/BUILD index 3d2483328e8..05843e9f17d 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/BUILD @@ -1,9 +1,40 @@ -package(default_visibility = ["//visibility:public"]) +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") -load( - "@io_bazel_rules_go//go:def.bzl", - "go_library", - "go_test", +go_library( + name = "go_default_library", + srcs = [ + "attributes.go", + "audit.go", + "chain.go", + "config.go", + "decorator.go", + "errors.go", + "handler.go", + "interfaces.go", + "plugins.go", + "reinvocation.go", + "util.go", + ], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission", + importpath = "k8s.io/apiserver/pkg/admission", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/apiserver:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/apis/audit:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/audit:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library", + "//vendor/k8s.io/klog:go_default_library", + "//vendor/sigs.k8s.io/yaml:go_default_library", + ], ) go_test( @@ -30,41 +61,6 @@ go_test( ], ) -go_library( - name = "go_default_library", - srcs = [ - "attributes.go", - "audit.go", - "chain.go", - "config.go", - "decorator.go", - "errors.go", - "handler.go", - "interfaces.go", - "plugins.go", - "util.go", - ], - importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission", - importpath = "k8s.io/apiserver/pkg/admission", - deps = [ - "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", - "//staging/src/k8s.io/apimachinery/pkg/util/validation:go_default_library", - "//staging/src/k8s.io/apiserver/pkg/apis/apiserver:go_default_library", - "//staging/src/k8s.io/apiserver/pkg/apis/apiserver/v1alpha1:go_default_library", - "//staging/src/k8s.io/apiserver/pkg/apis/audit:go_default_library", - "//staging/src/k8s.io/apiserver/pkg/audit:go_default_library", - "//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library", - "//vendor/k8s.io/klog:go_default_library", - "//vendor/sigs.k8s.io/yaml:go_default_library", - ], -) - filegroup( name = "package-srcs", srcs = glob(["**"]), @@ -80,19 +76,9 @@ filegroup( "//staging/src/k8s.io/apiserver/pkg/admission/initializer:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/metrics:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util:all-srcs", - "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:all-srcs", "//staging/src/k8s.io/apiserver/pkg/admission/testing:all-srcs", ], tags = ["automanaged"], + visibility = ["//visibility:public"], ) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/attributes.go b/staging/src/k8s.io/apiserver/pkg/admission/attributes.go index ad6ca6ba6fc..beea941fc30 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/attributes.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/attributes.go @@ -44,21 +44,24 @@ type attributesRecord struct { // But ValidatingAdmissionWebhook add annotations concurrently. annotations map[string]string annotationsLock sync.RWMutex + + reinvocationContext ReinvocationContext } func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, operationOptions runtime.Object, dryRun bool, userInfo user.Info) Attributes { return &attributesRecord{ - kind: kind, - namespace: namespace, - name: name, - resource: resource, - subresource: subresource, - operation: operation, - options: operationOptions, - dryRun: dryRun, - object: object, - oldObject: oldObject, - userInfo: userInfo, + kind: kind, + namespace: namespace, + name: name, + resource: resource, + subresource: subresource, + operation: operation, + options: operationOptions, + dryRun: dryRun, + object: object, + oldObject: oldObject, + userInfo: userInfo, + reinvocationContext: &reinvocationContext{}, } } @@ -140,6 +143,46 @@ func (record *attributesRecord) AddAnnotation(key, value string) error { return nil } +func (record *attributesRecord) GetReinvocationContext() ReinvocationContext { + return record.reinvocationContext +} + +type reinvocationContext struct { + // isReinvoke is true when admission plugins are being reinvoked + isReinvoke bool + // reinvokeRequested is true when an admission plugin requested a re-invocation of the chain + reinvokeRequested bool + // values stores reinvoke context values per plugin. + values map[string]interface{} +} + +func (rc *reinvocationContext) IsReinvoke() bool { + return rc.isReinvoke +} + +func (rc *reinvocationContext) SetIsReinvoke() { + rc.isReinvoke = true +} + +func (rc *reinvocationContext) ShouldReinvoke() bool { + return rc.reinvokeRequested +} + +func (rc *reinvocationContext) SetShouldReinvoke() { + rc.reinvokeRequested = true +} + +func (rc *reinvocationContext) SetValue(plugin string, v interface{}) { + if rc.values == nil { + rc.values = map[string]interface{}{} + } + rc.values[plugin] = v +} + +func (rc *reinvocationContext) Value(plugin string) interface{} { + return rc.values[plugin] +} + func checkKeyFormat(key string) error { parts := strings.Split(key, "/") if len(parts) != 2 { diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/configuration/BUILD index 34432781415..3b8448659b2 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/BUILD @@ -39,6 +39,7 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/wait:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library", "//staging/src/k8s.io/client-go/listers/admissionregistration/v1beta1:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go index 4b2256e118b..8cff4a254bf 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager.go @@ -24,6 +24,7 @@ import ( "k8s.io/api/admissionregistration/v1beta1" "k8s.io/apimachinery/pkg/labels" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/client-go/informers" admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1" @@ -48,7 +49,7 @@ func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) g } // Start with an empty list - manager.configuration.Store(&v1beta1.MutatingWebhookConfiguration{}) + manager.configuration.Store([]webhook.WebhookAccessor{}) // On any change, rebuild the config informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -61,8 +62,8 @@ func NewMutatingWebhookConfigurationManager(f informers.SharedInformerFactory) g } // Webhooks returns the merged MutatingWebhookConfiguration. -func (m *mutatingWebhookConfigurationManager) Webhooks() []v1beta1.Webhook { - return m.configuration.Load().(*v1beta1.MutatingWebhookConfiguration).Webhooks +func (m *mutatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor { + return m.configuration.Load().([]webhook.WebhookAccessor) } func (m *mutatingWebhookConfigurationManager) HasSynced() bool { @@ -78,16 +79,24 @@ func (m *mutatingWebhookConfigurationManager) updateConfiguration() { m.configuration.Store(mergeMutatingWebhookConfigurations(configurations)) } -func mergeMutatingWebhookConfigurations(configurations []*v1beta1.MutatingWebhookConfiguration) *v1beta1.MutatingWebhookConfiguration { - var ret v1beta1.MutatingWebhookConfiguration +func mergeMutatingWebhookConfigurations(configurations []*v1beta1.MutatingWebhookConfiguration) []webhook.WebhookAccessor { // The internal order of webhooks for each configuration is provided by the user // but configurations themselves can be in any order. As we are going to run these // webhooks in serial, they are sorted here to have a deterministic order. sort.SliceStable(configurations, MutatingWebhookConfigurationSorter(configurations).ByName) + accessors := []webhook.WebhookAccessor{} for _, c := range configurations { - ret.Webhooks = append(ret.Webhooks, c.Webhooks...) + // webhook names are not validated for uniqueness, so we check for duplicates and + // add a int suffix to distinguish between them + names := map[string]int{} + for i := range c.Webhooks { + n := c.Webhooks[i].Name + uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n]) + names[n]++ + accessors = append(accessors, webhook.NewMutatingWebhookAccessor(uid, &c.Webhooks[i])) + } } - return &ret + return accessors } type MutatingWebhookConfigurationSorter []*v1beta1.MutatingWebhookConfiguration diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go index 9bc037f5a40..828c1843025 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/mutating_webhook_manager_test.go @@ -45,7 +45,7 @@ func TestGetMutatingWebhookConfig(t *testing.T) { webhookConfiguration := &v1beta1.MutatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: "webhook1"}, - Webhooks: []v1beta1.Webhook{{Name: "webhook1.1"}}, + Webhooks: []v1beta1.MutatingWebhook{{Name: "webhook1.1"}}, } mutatingInformer := informerFactory.Admissionregistration().V1beta1().MutatingWebhookConfigurations() @@ -57,7 +57,14 @@ func TestGetMutatingWebhookConfig(t *testing.T) { if len(configurations) == 0 { t.Errorf("expected non empty webhooks") } - if !reflect.DeepEqual(configurations, webhookConfiguration.Webhooks) { - t.Errorf("Expected\n%#v\ngot\n%#v", webhookConfiguration.Webhooks, configurations) + for i := range configurations { + h, ok := configurations[i].GetMutatingWebhook() + if !ok { + t.Errorf("Expected mutating webhook") + continue + } + if !reflect.DeepEqual(h, &webhookConfiguration.Webhooks[i]) { + t.Errorf("Expected\n%#v\ngot\n%#v", &webhookConfiguration.Webhooks[i], h) + } } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go index 9258258f64b..804d83fe678 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager.go @@ -24,6 +24,7 @@ import ( "k8s.io/api/admissionregistration/v1beta1" "k8s.io/apimachinery/pkg/labels" utilruntime "k8s.io/apimachinery/pkg/util/runtime" + "k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/client-go/informers" admissionregistrationlisters "k8s.io/client-go/listers/admissionregistration/v1beta1" @@ -48,7 +49,7 @@ func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory) } // Start with an empty list - manager.configuration.Store(&v1beta1.ValidatingWebhookConfiguration{}) + manager.configuration.Store([]webhook.WebhookAccessor{}) // On any change, rebuild the config informer.Informer().AddEventHandler(cache.ResourceEventHandlerFuncs{ @@ -61,8 +62,8 @@ func NewValidatingWebhookConfigurationManager(f informers.SharedInformerFactory) } // Webhooks returns the merged ValidatingWebhookConfiguration. -func (v *validatingWebhookConfigurationManager) Webhooks() []v1beta1.Webhook { - return v.configuration.Load().(*v1beta1.ValidatingWebhookConfiguration).Webhooks +func (v *validatingWebhookConfigurationManager) Webhooks() []webhook.WebhookAccessor { + return v.configuration.Load().([]webhook.WebhookAccessor) } // HasSynced returns true if the shared informers have synced. @@ -79,15 +80,21 @@ func (v *validatingWebhookConfigurationManager) updateConfiguration() { v.configuration.Store(mergeValidatingWebhookConfigurations(configurations)) } -func mergeValidatingWebhookConfigurations( - configurations []*v1beta1.ValidatingWebhookConfiguration, -) *v1beta1.ValidatingWebhookConfiguration { +func mergeValidatingWebhookConfigurations(configurations []*v1beta1.ValidatingWebhookConfiguration) []webhook.WebhookAccessor { sort.SliceStable(configurations, ValidatingWebhookConfigurationSorter(configurations).ByName) - var ret v1beta1.ValidatingWebhookConfiguration + accessors := []webhook.WebhookAccessor{} for _, c := range configurations { - ret.Webhooks = append(ret.Webhooks, c.Webhooks...) + // webhook names are not validated for uniqueness, so we check for duplicates and + // add a int suffix to distinguish between them + names := map[string]int{} + for i := range c.Webhooks { + n := c.Webhooks[i].Name + uid := fmt.Sprintf("%s/%s/%d", c.Name, n, names[n]) + names[n]++ + accessors = append(accessors, webhook.NewValidatingWebhookAccessor(uid, &c.Webhooks[i])) + } } - return &ret + return accessors } type ValidatingWebhookConfigurationSorter []*v1beta1.ValidatingWebhookConfiguration diff --git a/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go index 153b4df486f..d99d99b46f2 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/configuration/validating_webhook_manager_test.go @@ -46,7 +46,7 @@ func TestGetValidatingWebhookConfig(t *testing.T) { webhookConfiguration := &v1beta1.ValidatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: "webhook1"}, - Webhooks: []v1beta1.Webhook{{Name: "webhook1.1"}}, + Webhooks: []v1beta1.ValidatingWebhook{{Name: "webhook1.1"}}, } validatingInformer := informerFactory.Admissionregistration().V1beta1().ValidatingWebhookConfigurations() @@ -59,7 +59,14 @@ func TestGetValidatingWebhookConfig(t *testing.T) { if len(configurations) == 0 { t.Errorf("expected non empty webhooks") } - if !reflect.DeepEqual(configurations, webhookConfiguration.Webhooks) { - t.Errorf("Expected\n%#v\ngot\n%#v", webhookConfiguration.Webhooks, configurations) + for i := range configurations { + h, ok := configurations[i].GetValidatingWebhook() + if !ok { + t.Errorf("Expected validating webhook") + continue + } + if !reflect.DeepEqual(h, &webhookConfiguration.Webhooks[i]) { + t.Errorf("Expected\n%#v\ngot\n%#v", &webhookConfiguration.Webhooks[i], h) + } } } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go b/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go index 040a6268b2f..5f6d703b216 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/interfaces.go @@ -62,6 +62,9 @@ type Attributes interface { // An error is returned if the format of key is invalid. When trying to overwrite annotation with a new value, an error is returned. // Both ValidationInterface and MutationInterface are allowed to add Annotations. AddAnnotation(key, value string) error + + // GetReinvocationContext tracks the admission request information relevant to the re-invocation policy. + GetReinvocationContext() ReinvocationContext } // ObjectInterfaces is an interface used by AdmissionController to get object interfaces @@ -91,6 +94,22 @@ type AnnotationsGetter interface { GetAnnotations() map[string]string } +// ReinvocationContext provides access to the admission related state required to implement the re-invocation policy. +type ReinvocationContext interface { + // IsReinvoke returns true if the current admission check is a re-invocation. + IsReinvoke() bool + // SetIsReinvoke sets the current admission check as a re-invocation. + SetIsReinvoke() + // ShouldReinvoke returns true if any plugin has requested a re-invocation. + ShouldReinvoke() bool + // SetShouldReinvoke signals that a re-invocation is desired. + SetShouldReinvoke() + // AddValue set a value for a plugin name, possibly overriding a previous value. + SetValue(plugin string, v interface{}) + // Value reads a value for a webhook. + Value(plugin string) interface{} +} + // Interface is an abstract, pluggable interface for Admission Control decisions. type Interface interface { // Handles returns true if this admission controller can handle the given operation diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/BUILD new file mode 100644 index 00000000000..c7d1cfa4556 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/BUILD @@ -0,0 +1,42 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library") + +go_library( + name = "go_default_library", + srcs = ["accessors.go"], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook", + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [ + ":package-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/initializer:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testcerts:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util:all-srcs", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating:all-srcs", + ], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go new file mode 100644 index 00000000000..b44c72ebfc9 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/accessors.go @@ -0,0 +1,160 @@ +/* +Copyright 2019 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 webhook + +import ( + "k8s.io/api/admissionregistration/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// WebhookAccessor provides a common interface to both mutating and validating webhook types. +type WebhookAccessor interface { + // GetUID gets a string that uniquely identifies the webhook. + GetUID() string + + // 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. + GetName() string + // GetClientConfig gets the webhook ClientConfig field. + GetClientConfig() v1beta1.WebhookClientConfig + // GetRules gets the webhook Rules field. + GetRules() []v1beta1.RuleWithOperations + // GetFailurePolicy gets the webhook FailurePolicy field. + GetFailurePolicy() *v1beta1.FailurePolicyType + // GetMatchPolicy gets the webhook MatchPolicy field. + GetMatchPolicy() *v1beta1.MatchPolicyType + // GetNamespaceSelector gets the webhook NamespaceSelector field. + GetNamespaceSelector() *metav1.LabelSelector + // GetObjectSelector gets the webhook ObjectSelector field. + GetObjectSelector() *metav1.LabelSelector + // GetSideEffects gets the webhook SideEffects field. + GetSideEffects() *v1beta1.SideEffectClass + // GetTimeoutSeconds gets the webhook TimeoutSeconds field. + GetTimeoutSeconds() *int32 + // GetAdmissionReviewVersions gets the webhook AdmissionReviewVersions field. + GetAdmissionReviewVersions() []string + + // GetMutatingWebhook if the accessor contains a MutatingWebhook, returns it and true, else returns false. + GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) + // GetValidatingWebhook if the accessor contains a ValidatingWebhook, returns it and true, else returns false. + GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) +} + +// NewMutatingWebhookAccessor creates an accessor for a MutatingWebhook. +func NewMutatingWebhookAccessor(uid string, h *v1beta1.MutatingWebhook) WebhookAccessor { + return mutatingWebhookAccessor{uid: uid, MutatingWebhook: h} +} + +type mutatingWebhookAccessor struct { + *v1beta1.MutatingWebhook + uid string +} + +func (m mutatingWebhookAccessor) GetUID() string { + return m.Name +} +func (m mutatingWebhookAccessor) GetName() string { + return m.Name +} +func (m mutatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig { + return m.ClientConfig +} +func (m mutatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations { + return m.Rules +} +func (m mutatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType { + return m.FailurePolicy +} +func (m mutatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType { + return m.MatchPolicy +} +func (m mutatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector { + return m.NamespaceSelector +} +func (m mutatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector { + return m.ObjectSelector +} +func (m mutatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass { + return m.SideEffects +} +func (m mutatingWebhookAccessor) GetTimeoutSeconds() *int32 { + return m.TimeoutSeconds +} +func (m mutatingWebhookAccessor) GetAdmissionReviewVersions() []string { + return m.AdmissionReviewVersions +} + +func (m mutatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) { + return m.MutatingWebhook, true +} + +func (m mutatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) { + return nil, false +} + +// NewValidatingWebhookAccessor creates an accessor for a ValidatingWebhook. +func NewValidatingWebhookAccessor(uid string, h *v1beta1.ValidatingWebhook) WebhookAccessor { + return validatingWebhookAccessor{uid: uid, ValidatingWebhook: h} +} + +type validatingWebhookAccessor struct { + *v1beta1.ValidatingWebhook + uid string +} + +func (v validatingWebhookAccessor) GetUID() string { + return v.uid +} +func (v validatingWebhookAccessor) GetName() string { + return v.Name +} +func (v validatingWebhookAccessor) GetClientConfig() v1beta1.WebhookClientConfig { + return v.ClientConfig +} +func (v validatingWebhookAccessor) GetRules() []v1beta1.RuleWithOperations { + return v.Rules +} +func (v validatingWebhookAccessor) GetFailurePolicy() *v1beta1.FailurePolicyType { + return v.FailurePolicy +} +func (v validatingWebhookAccessor) GetMatchPolicy() *v1beta1.MatchPolicyType { + return v.MatchPolicy +} +func (v validatingWebhookAccessor) GetNamespaceSelector() *metav1.LabelSelector { + return v.NamespaceSelector +} +func (v validatingWebhookAccessor) GetObjectSelector() *metav1.LabelSelector { + return v.ObjectSelector +} +func (v validatingWebhookAccessor) GetSideEffects() *v1beta1.SideEffectClass { + return v.SideEffects +} +func (v validatingWebhookAccessor) GetTimeoutSeconds() *int32 { + return v.TimeoutSeconds +} +func (v validatingWebhookAccessor) GetAdmissionReviewVersions() []string { + return v.AdmissionReviewVersions +} + +func (v validatingWebhookAccessor) GetMutatingWebhook() (*v1beta1.MutatingWebhook, bool) { + return nil, false +} + +func (v validatingWebhookAccessor) GetValidatingWebhook() (*v1beta1.ValidatingWebhook, bool) { + return v.ValidatingWebhook, true +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/BUILD index 7cdf2e46b48..6572d3d6103 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/BUILD @@ -18,8 +18,10 @@ go_library( "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/initializer:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/config:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/rules:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library", "//staging/src/k8s.io/client-go/informers:go_default_library", @@ -56,7 +58,9 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object:go_default_library", "//staging/src/k8s.io/apiserver/pkg/apis/example:go_default_library", "//staging/src/k8s.io/apiserver/pkg/apis/example/v1:go_default_library", "//staging/src/k8s.io/apiserver/pkg/apis/example2/v1:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go index c57189543fb..4381691ef81 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/interfaces.go @@ -19,15 +19,15 @@ package generic import ( "context" - "k8s.io/api/admissionregistration/v1beta1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook" ) // Source can list dynamic webhook plugins. type Source interface { - Webhooks() []v1beta1.Webhook + Webhooks() []webhook.WebhookAccessor HasSynced() bool } @@ -35,7 +35,7 @@ type Source interface { // variants of the object and old object. type VersionedAttributes struct { // Attributes holds the original admission attributes - Attributes admission.Attributes + admission.Attributes // VersionedOldObject holds Attributes.OldObject (if non-nil), converted to VersionedKind. // It must never be mutated. VersionedOldObject runtime.Object @@ -48,11 +48,18 @@ type VersionedAttributes struct { Dirty bool } +// GetObject overrides the Attributes.GetObject() +func (v *VersionedAttributes) GetObject() runtime.Object { + if v.VersionedObject != nil { + return v.VersionedObject + } + return v.Attributes.GetObject() +} + // WebhookInvocation describes how to call a webhook, including the resource and subresource the webhook registered for, // and the kind that should be sent to the webhook. type WebhookInvocation struct { - Webhook *v1beta1.Webhook - + Webhook webhook.WebhookAccessor Resource schema.GroupVersionResource Subresource string Kind schema.GroupVersionKind @@ -60,6 +67,9 @@ type WebhookInvocation struct { // Dispatcher dispatches webhook call to a list of webhooks with admission attributes as argument. type Dispatcher interface { - // Dispatch a request to the webhooks using the given webhooks. A non-nil error means the request is rejected. - Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []*WebhookInvocation) error + // Dispatch a request to the webhooks. Dispatcher may choose not to + // call a hook, either because the rules of the hook does not match, or + // the namespaceSelector or the objectSelector of the hook does not + // match. A non-nil error means the request is rejected. + Dispatch(ctx context.Context, a admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go index 30a9b5e7b5e..e88125ff5b8 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook.go @@ -27,10 +27,12 @@ import ( "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" genericadmissioninit "k8s.io/apiserver/pkg/admission/initializer" + "k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/apiserver/pkg/admission/plugin/webhook/config" "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace" + "k8s.io/apiserver/pkg/admission/plugin/webhook/object" "k8s.io/apiserver/pkg/admission/plugin/webhook/rules" - "k8s.io/apiserver/pkg/util/webhook" + webhookutil "k8s.io/apiserver/pkg/util/webhook" "k8s.io/client-go/informers" clientset "k8s.io/client-go/kubernetes" ) @@ -42,8 +44,9 @@ type Webhook struct { sourceFactory sourceFactory hookSource Source - clientManager *webhook.ClientManager + clientManager *webhookutil.ClientManager namespaceMatcher *namespace.Matcher + objectMatcher *object.Matcher dispatcher Dispatcher } @@ -53,7 +56,7 @@ var ( ) type sourceFactory func(f informers.SharedInformerFactory) Source -type dispatcherFactory func(cm *webhook.ClientManager) Dispatcher +type dispatcherFactory func(cm *webhookutil.ClientManager) Dispatcher // NewWebhook creates a new generic admission webhook. func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory sourceFactory, dispatcherFactory dispatcherFactory) (*Webhook, error) { @@ -62,23 +65,24 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory return nil, err } - cm, err := webhook.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme) + cm, err := webhookutil.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme) if err != nil { return nil, err } - authInfoResolver, err := webhook.NewDefaultAuthenticationInfoResolver(kubeconfigFile) + authInfoResolver, err := webhookutil.NewDefaultAuthenticationInfoResolver(kubeconfigFile) if err != nil { return nil, err } // Set defaults which may be overridden later. cm.SetAuthenticationInfoResolver(authInfoResolver) - cm.SetServiceResolver(webhook.NewDefaultServiceResolver()) + cm.SetServiceResolver(webhookutil.NewDefaultServiceResolver()) return &Webhook{ Handler: handler, sourceFactory: sourceFactory, clientManager: &cm, namespaceMatcher: &namespace.Matcher{}, + objectMatcher: &object.Matcher{}, dispatcher: dispatcherFactory(&cm), }, nil } @@ -86,13 +90,13 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory // SetAuthenticationInfoResolverWrapper sets the // AuthenticationInfoResolverWrapper. // TODO find a better way wire this, but keep this pull small for now. -func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhook.AuthenticationInfoResolverWrapper) { +func (a *Webhook) SetAuthenticationInfoResolverWrapper(wrapper webhookutil.AuthenticationInfoResolverWrapper) { a.clientManager.SetAuthenticationInfoResolverWrapper(wrapper) } // SetServiceResolver sets a service resolver for the webhook admission plugin. // Passing a nil resolver does not have an effect, instead a default one will be used. -func (a *Webhook) SetServiceResolver(sr webhook.ServiceResolver) { +func (a *Webhook) SetServiceResolver(sr webhookutil.ServiceResolver) { a.clientManager.SetServiceResolver(sr) } @@ -126,12 +130,12 @@ func (a *Webhook) ValidateInitialization() error { return nil } -// shouldCallHook returns invocation details if the webhook should be called, nil if the webhook should not be called, +// 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 *v1beta1.Webhook, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) { +func (a *Webhook) ShouldCallHook(h webhook.WebhookAccessor, attr admission.Attributes, o admission.ObjectInterfaces) (*WebhookInvocation, *apierrors.StatusError) { var err *apierrors.StatusError var invocation *WebhookInvocation - for _, r := range h.Rules { + for _, r := range h.GetRules() { m := rules.Matcher{Rule: r, Attr: attr} if m.Matches() { invocation = &WebhookInvocation{ @@ -143,12 +147,12 @@ func (a *Webhook) shouldCallHook(h *v1beta1.Webhook, attr admission.Attributes, break } } - if invocation == nil && h.MatchPolicy != nil && *h.MatchPolicy == v1beta1.Equivalent { + if invocation == nil && h.GetMatchPolicy() != nil && *h.GetMatchPolicy() == v1beta1.Equivalent { attrWithOverride := &attrWithResourceOverride{Attributes: attr} equivalents := o.GetEquivalentResourceMapper().EquivalentResourcesFor(attr.GetResource(), attr.GetSubresource()) // honor earlier rules first OuterLoop: - for _, r := range h.Rules { + for _, r := range h.GetRules() { // see if the rule matches any of the equivalent resources for _, equivalent := range equivalents { if equivalent == attr.GetResource() { @@ -183,6 +187,11 @@ func (a *Webhook) shouldCallHook(h *v1beta1.Webhook, attr admission.Attributes, return nil, err } + matches, err = a.objectMatcher.MatchObjectSelector(h, attr) + if !matches || err != nil { + return nil, err + } + return invocation, nil } @@ -205,21 +214,5 @@ func (a *Webhook) Dispatch(attr admission.Attributes, o admission.ObjectInterfac // TODO: Figure out if adding one second timeout make sense here. ctx := context.TODO() - var relevantHooks []*WebhookInvocation - for i := range hooks { - invocation, err := a.shouldCallHook(&hooks[i], attr, o) - if err != nil { - return err - } - if invocation != nil { - relevantHooks = append(relevantHooks, invocation) - } - } - - if len(relevantHooks) == 0 { - // no matching hooks - return nil - } - - return a.dispatcher.Dispatch(ctx, attr, o, relevantHooks) + return a.dispatcher.Dispatch(ctx, attr, o, hooks) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook_test.go index b214122ad38..ad7fc7896e9 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic/webhook_test.go @@ -17,6 +17,7 @@ limitations under the License. package generic import ( + "fmt" "strings" "testing" @@ -25,11 +26,13 @@ import ( "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook" "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace" + "k8s.io/apiserver/pkg/admission/plugin/webhook/object" ) func TestShouldCallHook(t *testing.T) { - a := &Webhook{namespaceMatcher: &namespace.Matcher{}} + a := &Webhook{namespaceMatcher: &namespace.Matcher{}, objectMatcher: &object.Matcher{}} allScopes := v1beta1.AllScopes exactMatch := v1beta1.Exact @@ -61,7 +64,7 @@ func TestShouldCallHook(t *testing.T) { testcases := []struct { name string - webhook *v1beta1.Webhook + webhook *v1beta1.ValidatingWebhook attrs admission.Attributes expectCall bool @@ -72,14 +75,15 @@ func TestShouldCallHook(t *testing.T) { }{ { name: "no rules (just write)", - webhook: &v1beta1.Webhook{Rules: []v1beta1.RuleWithOperations{}}, + webhook: &v1beta1.ValidatingWebhook{Rules: []v1beta1.RuleWithOperations{}}, attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{"apps", "v1", "Deployment"}, "ns", "name", schema.GroupVersionResource{"apps", "v1", "deployments"}, "", admission.Create, &metav1.CreateOptions{}, false, nil), expectCall: false, }, { name: "invalid kind lookup", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, MatchPolicy: &equivalentMatch, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, @@ -91,8 +95,9 @@ func TestShouldCallHook(t *testing.T) { }, { name: "wildcard rule, match as requested", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, @@ -105,8 +110,9 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, prefer exact match", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, @@ -125,8 +131,9 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, match miss", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, @@ -139,9 +146,10 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, exact match miss", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ MatchPolicy: &exactMatch, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, @@ -154,9 +162,10 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, equivalent match, prefer extensions", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ MatchPolicy: &equivalentMatch, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, @@ -172,9 +181,10 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, equivalent match, prefer apps", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ MatchPolicy: &equivalentMatch, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments"}, Scope: &allScopes}, @@ -191,8 +201,9 @@ func TestShouldCallHook(t *testing.T) { { name: "specific rules, subresource prefer exact match", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, @@ -211,8 +222,9 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, subresource match miss", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, @@ -225,9 +237,10 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, subresource exact match miss", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ MatchPolicy: &exactMatch, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, @@ -240,9 +253,10 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, subresource equivalent match, prefer extensions", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ MatchPolicy: &equivalentMatch, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"extensions"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, @@ -258,9 +272,10 @@ func TestShouldCallHook(t *testing.T) { }, { name: "specific rules, subresource equivalent match, prefer apps", - webhook: &v1beta1.Webhook{ + webhook: &v1beta1.ValidatingWebhook{ MatchPolicy: &equivalentMatch, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{"*"}, Rule: v1beta1.Rule{APIGroups: []string{"apps"}, APIVersions: []string{"v1beta1"}, Resources: []string{"deployments", "deployments/scale"}, Scope: &allScopes}, @@ -276,9 +291,9 @@ func TestShouldCallHook(t *testing.T) { }, } - for _, testcase := range testcases { + for i, testcase := range testcases { t.Run(testcase.name, func(t *testing.T) { - invocation, err := a.shouldCallHook(testcase.webhook, testcase.attrs, interfaces) + invocation, err := a.ShouldCallHook(webhook.NewValidatingWebhookAccessor(fmt.Sprintf("webhook-%d", i), testcase.webhook), testcase.attrs, interfaces) if err != nil { if len(testcase.expectErr) == 0 { t.Fatal(err) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/BUILD index 903afb8753b..0fe2429c86c 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/BUILD @@ -6,6 +6,7 @@ go_library( "dispatcher.go", "doc.go", "plugin.go", + "reinvocationcontext.go", ], importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating", importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating", @@ -13,14 +14,17 @@ go_library( deps = [ "//staging/src/k8s.io/api/admission/v1beta1:go_default_library", "//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/serializer/json:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/configuration:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/metrics:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go index 33190ff1214..5c5c41aedbd 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/dispatcher.go @@ -24,6 +24,7 @@ import ( "time" jsonpatch "github.com/evanphx/json-patch" + apiequality "k8s.io/apimachinery/pkg/api/equality" "k8s.io/klog" admissionv1beta1 "k8s.io/api/admission/v1beta1" @@ -35,30 +36,71 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/admission" admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" + "k8s.io/apiserver/pkg/admission/plugin/webhook" webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/apiserver/pkg/admission/plugin/webhook/request" "k8s.io/apiserver/pkg/admission/plugin/webhook/util" - "k8s.io/apiserver/pkg/util/webhook" + webhookutil "k8s.io/apiserver/pkg/util/webhook" ) type mutatingDispatcher struct { - cm *webhook.ClientManager + cm *webhookutil.ClientManager plugin *Plugin } -func newMutatingDispatcher(p *Plugin) func(cm *webhook.ClientManager) generic.Dispatcher { - return func(cm *webhook.ClientManager) generic.Dispatcher { +func newMutatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher { + return func(cm *webhookutil.ClientManager) generic.Dispatcher { return &mutatingDispatcher{cm, p} } } var _ generic.Dispatcher = &mutatingDispatcher{} -func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, relevantHooks []*generic.WebhookInvocation) error { +func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, hooks []webhook.WebhookAccessor) error { + reinvokeCtx := attr.GetReinvocationContext() + var webhookReinvokeCtx *webhookReinvokeContext + if v := reinvokeCtx.Value(PluginName); v != nil { + webhookReinvokeCtx = v.(*webhookReinvokeContext) + } else { + webhookReinvokeCtx = &webhookReinvokeContext{} + reinvokeCtx.SetValue(PluginName, webhookReinvokeCtx) + } + + if reinvokeCtx.IsReinvoke() && webhookReinvokeCtx.IsOutputChangedSinceLastWebhookInvocation(attr.GetObject()) { + // If the object has changed, we know the in-tree plugin re-invocations have mutated the object, + // and we need to reinvoke all eligible webhooks. + webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins() + } + defer func() { + webhookReinvokeCtx.SetLastWebhookInvocationOutput(attr.GetObject()) + }() var versionedAttr *generic.VersionedAttributes - for _, invocation := range relevantHooks { - hook := invocation.Webhook + for _, hook := range hooks { + attrForCheck := attr + if versionedAttr != nil { + attrForCheck = versionedAttr + } + invocation, statusErr := a.plugin.ShouldCallHook(hook, attrForCheck, o) + if statusErr != nil { + return statusErr + } + if invocation == nil { + continue + } + hook, ok := invocation.Webhook.GetMutatingWebhook() + if !ok { + return fmt.Errorf("mutating webhook dispatch requires v1beta1.MutatingWebhook, but got %T", hook) + } + // This means that during reinvocation, a webhook will not be + // called for the first time. For example, if the webhook is + // skipped in the first round because of mismatching labels, + // even if the labels become matching, the webhook does not + // get called during reinvocation. + if reinvokeCtx.IsReinvoke() && !webhookReinvokeCtx.ShouldReinvokeWebhook(invocation.Webhook.GetUID()) { + continue + } + if versionedAttr == nil { // First webhook, create versioned attributes var err error @@ -73,14 +115,23 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib } t := time.Now() - err := a.callAttrMutatingHook(ctx, invocation, versionedAttr, o) + + changed, err := a.callAttrMutatingHook(ctx, hook, invocation, versionedAttr, o) admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, versionedAttr.Attributes, "admit", hook.Name) + if changed { + // Patch had changed the object. Prepare to reinvoke all previous webhooks that are eligible for re-invocation. + webhookReinvokeCtx.RequireReinvokingPreviouslyInvokedPlugins() + reinvokeCtx.SetShouldReinvoke() + } + if hook.ReinvocationPolicy != nil && *hook.ReinvocationPolicy == v1beta1.IfNeededReinvocationPolicy { + webhookReinvokeCtx.AddReinvocableWebhookToPreviouslyInvoked(invocation.Webhook.GetUID()) + } if err == nil { continue } ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore - if callErr, ok := err.(*webhook.ErrCallingWebhook); ok { + if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok { if ignoreClientCallFailures { klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr) utilruntime.HandleError(callErr) @@ -96,32 +147,33 @@ func (a *mutatingDispatcher) Dispatch(ctx context.Context, attr admission.Attrib if versionedAttr != nil && versionedAttr.VersionedObject != nil && versionedAttr.Dirty { return o.GetObjectConvertor().Convert(versionedAttr.VersionedObject, versionedAttr.Attributes.GetObject(), nil) } + return nil } // note that callAttrMutatingHook updates attr -func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes, o admission.ObjectInterfaces) error { - h := invocation.Webhook + +func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta1.MutatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes, o admission.ObjectInterfaces) (bool, error) { if attr.Attributes.IsDryRun() { if h.SideEffects == nil { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} + return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} } if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) { - return webhookerrors.NewDryRunUnsupportedErr(h.Name) + return false, webhookerrors.NewDryRunUnsupportedErr(h.Name) } } // Currently dispatcher only supports `v1beta1` AdmissionReview // TODO: Make the dispatcher capable of sending multiple AdmissionReview versions - if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")} + if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, invocation.Webhook) { + return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")} } // Make the webhook request request := request.CreateAdmissionReview(attr, invocation) - client, err := a.cm.HookClient(util.HookClientConfigForWebhook(h)) + client, err := a.cm.HookClient(util.HookClientConfigForWebhook(invocation.Webhook)) if err != nil { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} + return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err} } response := &admissionv1beta1.AdmissionReview{} r := client.Post().Context(ctx).Body(&request) @@ -129,11 +181,11 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, invocatio r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second) } if err := r.Do().Into(response); err != nil { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} + return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err} } if response.Response == nil { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} + return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} } for k, v := range response.Response.AuditAnnotations { @@ -144,34 +196,34 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, invocatio } if !response.Response.Allowed { - return webhookerrors.ToStatusErr(h.Name, response.Response.Result) + return false, webhookerrors.ToStatusErr(h.Name, response.Response.Result) } patchJS := response.Response.Patch if len(patchJS) == 0 { - return nil + return false, nil } patchObj, err := jsonpatch.DecodePatch(patchJS) if err != nil { - return apierrors.NewInternalError(err) + return false, apierrors.NewInternalError(err) } if len(patchObj) == 0 { - return nil + return false, nil } // if a non-empty patch was provided, and we have no object we can apply it to (e.g. a DELETE admission operation), error if attr.VersionedObject == nil { - return apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name)) + return false, apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name)) } jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), false) objJS, err := runtime.Encode(jsonSerializer, attr.VersionedObject) if err != nil { - return apierrors.NewInternalError(err) + return false, apierrors.NewInternalError(err) } patchedJS, err := patchObj.Apply(objJS) if err != nil { - return apierrors.NewInternalError(err) + return false, apierrors.NewInternalError(err) } var newVersionedObject runtime.Object @@ -182,16 +234,20 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, invocatio } else { newVersionedObject, err = o.GetObjectCreater().New(attr.VersionedKind) if err != nil { - return apierrors.NewInternalError(err) + return false, apierrors.NewInternalError(err) } } + // TODO: if we have multiple mutating webhooks, we can remember the json // instead of encoding and decoding for each one. if newVersionedObject, _, err = jsonSerializer.Decode(patchedJS, nil, newVersionedObject); err != nil { - return apierrors.NewInternalError(err) + return false, apierrors.NewInternalError(err) } + + changed := !apiequality.Semantic.DeepEqual(attr.VersionedObject, newVersionedObject) + attr.Dirty = true attr.VersionedObject = newVersionedObject o.GetObjectDefaulter().Default(attr.VersionedObject) - return nil + return changed, nil } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go index 4a1a1c328b4..b618e309149 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/plugin_test.go @@ -46,70 +46,80 @@ func TestAdmit(t *testing.T) { defer close(stopCh) testCases := append(webhooktesting.NewMutatingTestCases(serverURL), - webhooktesting.NewNonMutatingTestCases(serverURL)...) + webhooktesting.ConvertToMutatingTestCases(webhooktesting.NewNonMutatingTestCases(serverURL))...) for _, tt := range testCases { - wh, err := NewMutatingWebhook(nil) - if err != nil { - t.Errorf("%s: failed to create mutating webhook: %v", tt.Name, err) - continue - } - - ns := "webhook-test" - client, informer := webhooktesting.NewFakeDataSource(ns, tt.Webhooks, true, stopCh) - - wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32)))) - wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL)) - wh.SetExternalKubeClientSet(client) - wh.SetExternalKubeInformerFactory(informer) - - informer.Start(stopCh) - informer.WaitForCacheSync(stopCh) - - if err = wh.ValidateInitialization(); err != nil { - t.Errorf("%s: failed to validate initialization: %v", tt.Name, err) - continue - } - - var attr admission.Attributes - if tt.IsCRD { - attr = webhooktesting.NewAttributeUnstructured(ns, tt.AdditionalLabels, tt.IsDryRun) - } else { - attr = webhooktesting.NewAttribute(ns, tt.AdditionalLabels, tt.IsDryRun) - } - - err = wh.Admit(attr, objectInterfaces) - if tt.ExpectAllow != (err == nil) { - t.Errorf("%s: expected allowed=%v, but got err=%v", tt.Name, tt.ExpectAllow, err) - } - if tt.ExpectLabels != nil { - if !reflect.DeepEqual(tt.ExpectLabels, attr.GetObject().(metav1.Object).GetLabels()) { - t.Errorf("%s: expected labels '%v', but got '%v'", tt.Name, tt.ExpectLabels, attr.GetObject().(metav1.Object).GetLabels()) + t.Run(tt.Name, func(t *testing.T) { + wh, err := NewMutatingWebhook(nil) + if err != nil { + t.Errorf("failed to create mutating webhook: %v", err) + return } - } - // ErrWebhookRejected is not an error for our purposes - if tt.ErrorContains != "" { - if err == nil || !strings.Contains(err.Error(), tt.ErrorContains) { - t.Errorf("%s: expected an error saying %q, but got: %v", tt.Name, tt.ErrorContains, err) + + ns := "webhook-test" + client, informer := webhooktesting.NewFakeMutatingDataSource(ns, tt.Webhooks, stopCh) + + wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32)))) + wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL)) + wh.SetExternalKubeClientSet(client) + wh.SetExternalKubeInformerFactory(informer) + + informer.Start(stopCh) + informer.WaitForCacheSync(stopCh) + + if err = wh.ValidateInitialization(); err != nil { + t.Errorf("failed to validate initialization: %v", err) + return } - } - if statusErr, isStatusErr := err.(*errors.StatusError); err != nil && !isStatusErr { - t.Errorf("%s: expected a StatusError, got %T", tt.Name, err) - } else if isStatusErr { - if statusErr.ErrStatus.Code != tt.ExpectStatusCode { - t.Errorf("%s: expected status code %d, got %d", tt.Name, tt.ExpectStatusCode, statusErr.ErrStatus.Code) + + var attr admission.Attributes + if tt.IsCRD { + attr = webhooktesting.NewAttributeUnstructured(ns, tt.AdditionalLabels, tt.IsDryRun) + } else { + attr = webhooktesting.NewAttribute(ns, tt.AdditionalLabels, tt.IsDryRun) } - } - fakeAttr, ok := attr.(*webhooktesting.FakeAttributes) - if !ok { - t.Errorf("Unexpected error, failed to convert attr to webhooktesting.FakeAttributes") - continue - } - if len(tt.ExpectAnnotations) == 0 { - assert.Empty(t, fakeAttr.GetAnnotations(), tt.Name+": annotations not set as expected.") - } else { - assert.Equal(t, tt.ExpectAnnotations, fakeAttr.GetAnnotations(), tt.Name+": annotations not set as expected.") - } + + err = wh.Admit(attr, objectInterfaces) + if tt.ExpectAllow != (err == nil) { + t.Errorf("expected allowed=%v, but got err=%v", tt.ExpectAllow, err) + } + if tt.ExpectLabels != nil { + if !reflect.DeepEqual(tt.ExpectLabels, attr.GetObject().(metav1.Object).GetLabels()) { + t.Errorf("expected labels '%v', but got '%v'", tt.ExpectLabels, attr.GetObject().(metav1.Object).GetLabels()) + } + } + // ErrWebhookRejected is not an error for our purposes + if tt.ErrorContains != "" { + if err == nil || !strings.Contains(err.Error(), tt.ErrorContains) { + t.Errorf("expected an error saying %q, but got: %v", tt.ErrorContains, err) + } + } + if statusErr, isStatusErr := err.(*errors.StatusError); err != nil && !isStatusErr { + t.Errorf("expected a StatusError, got %T", err) + } else if isStatusErr { + if statusErr.ErrStatus.Code != tt.ExpectStatusCode { + t.Errorf("expected status code %d, got %d", tt.ExpectStatusCode, statusErr.ErrStatus.Code) + } + } + fakeAttr, ok := attr.(*webhooktesting.FakeAttributes) + if !ok { + t.Errorf("Unexpected error, failed to convert attr to webhooktesting.FakeAttributes") + return + } + if len(tt.ExpectAnnotations) == 0 { + assert.Empty(t, fakeAttr.GetAnnotations(), tt.Name+": annotations not set as expected.") + } else { + assert.Equal(t, tt.ExpectAnnotations, fakeAttr.GetAnnotations(), tt.Name+": annotations not set as expected.") + } + reinvocationCtx := fakeAttr.Attributes.GetReinvocationContext() + reinvocationCtx.SetIsReinvoke() + for webhook, expectReinvoke := range tt.ExpectReinvokeWebhooks { + shouldReinvoke := reinvocationCtx.Value(PluginName).(*webhookReinvokeContext).ShouldReinvokeWebhook(webhook) + if expectReinvoke != shouldReinvoke { + t.Errorf("expected reinvocationContext.ShouldReinvokeWebhook(%s)=%t, but got %t", webhook, expectReinvoke, shouldReinvoke) + } + } + }) } } @@ -136,7 +146,7 @@ func TestAdmitCachedClient(t *testing.T) { for _, tt := range webhooktesting.NewCachedClientTestcases(serverURL) { ns := "webhook-test" - client, informer := webhooktesting.NewFakeDataSource(ns, tt.Webhooks, true, stopCh) + client, informer := webhooktesting.NewFakeMutatingDataSource(ns, webhooktesting.ConvertToMutatingWebhooks(tt.Webhooks), stopCh) // override the webhook source. The client cache will stay the same. cacheMisses := new(int32) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go new file mode 100644 index 00000000000..de0af221e13 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/mutating/reinvocationcontext.go @@ -0,0 +1,68 @@ +/* +Copyright 2019 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 mutating + +import ( + apiequality "k8s.io/apimachinery/pkg/api/equality" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/util/sets" +) + +type webhookReinvokeContext struct { + // lastWebhookOutput holds the result of the last webhook admission plugin call + lastWebhookOutput runtime.Object + // previouslyInvokedReinvocableWebhooks holds the set of webhooks that have been invoked and + // should be reinvoked if a later mutation occurs + previouslyInvokedReinvocableWebhooks sets.String + // reinvokeWebhooks holds the set of webhooks that should be reinvoked + reinvokeWebhooks sets.String +} + +func (rc *webhookReinvokeContext) ShouldReinvokeWebhook(webhook string) bool { + return rc.reinvokeWebhooks.Has(webhook) +} + +func (rc *webhookReinvokeContext) IsOutputChangedSinceLastWebhookInvocation(object runtime.Object) bool { + return !apiequality.Semantic.DeepEqual(rc.lastWebhookOutput, object) +} + +func (rc *webhookReinvokeContext) SetLastWebhookInvocationOutput(object runtime.Object) { + if object == nil { + rc.lastWebhookOutput = nil + return + } + rc.lastWebhookOutput = object.DeepCopyObject() +} + +func (rc *webhookReinvokeContext) AddReinvocableWebhookToPreviouslyInvoked(webhook string) { + if rc.previouslyInvokedReinvocableWebhooks == nil { + rc.previouslyInvokedReinvocableWebhooks = sets.NewString() + } + rc.previouslyInvokedReinvocableWebhooks.Insert(webhook) +} + +func (rc *webhookReinvokeContext) RequireReinvokingPreviouslyInvokedPlugins() { + if len(rc.previouslyInvokedReinvocableWebhooks) > 0 { + if rc.reinvokeWebhooks == nil { + rc.reinvokeWebhooks = sets.NewString() + } + for s := range rc.previouslyInvokedReinvocableWebhooks { + rc.reinvokeWebhooks.Insert(s) + } + rc.previouslyInvokedReinvocableWebhooks = sets.NewString() + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/BUILD index 6950843f754..24198a9ee3f 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/BUILD @@ -10,13 +10,13 @@ go_library( importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/namespace", visibility = ["//visibility:public"], deps = [ - "//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", "//staging/src/k8s.io/client-go/kubernetes:go_default_library", "//staging/src/k8s.io/client-go/listers/core/v1:go_default_library", ], @@ -34,6 +34,7 @@ go_test( "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go index 78dbd528286..4530de3e608 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher.go @@ -19,13 +19,13 @@ package namespace import ( "fmt" - "k8s.io/api/admissionregistration/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/labels" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook" clientset "k8s.io/client-go/kubernetes" corelisters "k8s.io/client-go/listers/core/v1" ) @@ -86,7 +86,7 @@ func (m *Matcher) GetNamespaceLabels(attr admission.Attributes) (map[string]stri // MatchNamespaceSelector decideds whether the request matches the // namespaceSelctor of the webhook. Only when they match, the webhook is called. -func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attributes) (bool, *apierrors.StatusError) { +func (m *Matcher) MatchNamespaceSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) { namespaceName := attr.GetNamespace() if len(namespaceName) == 0 && attr.GetResource().Resource != "namespaces" { // If the request is about a cluster scoped resource, and it is not a @@ -96,7 +96,7 @@ func (m *Matcher) MatchNamespaceSelector(h *v1beta1.Webhook, attr admission.Attr return true, nil } // TODO: adding an LRU cache to cache the translation - selector, err := metav1.LabelSelectorAsSelector(h.NamespaceSelector) + selector, err := metav1.LabelSelectorAsSelector(h.GetNamespaceSelector()) if err != nil { return false, apierrors.NewInternalError(err) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher_test.go index 616ff7bbe31..7bdf6c4509b 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/namespace/matcher_test.go @@ -27,6 +27,7 @@ import ( "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook" ) type fakeNamespaceLister struct { @@ -114,12 +115,12 @@ func TestGetNamespaceLabels(t *testing.T) { } func TestNotExemptClusterScopedResource(t *testing.T) { - hook := ®istrationv1beta1.Webhook{ + hook := ®istrationv1beta1.ValidatingWebhook{ NamespaceSelector: &metav1.LabelSelector{}, } attr := admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "mock-name", schema.GroupVersionResource{Version: "v1", Resource: "nodes"}, "", admission.Create, &metav1.CreateOptions{}, false, nil) matcher := Matcher{} - matches, err := matcher.MatchNamespaceSelector(hook, attr) + matches, err := matcher.MatchNamespaceSelector(webhook.NewValidatingWebhookAccessor("mock-hook", hook), attr) if err != nil { t.Fatal(err) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/BUILD new file mode 100644 index 00000000000..6a069ce315b --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/BUILD @@ -0,0 +1,50 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "doc.go", + "matcher.go", + ], + importmap = "k8s.io/kubernetes/vendor/k8s.io/apiserver/pkg/admission/plugin/webhook/object", + importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/object", + visibility = ["//visibility:public"], + deps = [ + "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/api/meta:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/labels:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", + "//vendor/k8s.io/klog:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) + +go_test( + name = "go_default_test", + srcs = ["matcher_test.go"], + embed = [":go_default_library"], + deps = [ + "//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//staging/src/k8s.io/api/core/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", + ], +) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/doc.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/doc.go new file mode 100644 index 00000000000..93c47344095 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/doc.go @@ -0,0 +1,20 @@ +/* +Copyright 2019 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 object defines the utilities that are used by the webhook plugin to +// decide if a webhook should run, as long as either the old object or the new +// object has labels matching the webhook config's objectSelector. +package object // import "k8s.io/apiserver/pkg/admission/plugin/webhook/object" diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go new file mode 100644 index 00000000000..be341dd95c9 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher.go @@ -0,0 +1,59 @@ +/* +Copyright 2019 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 object + +import ( + apierrors "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook" + "k8s.io/klog" +) + +// Matcher decides if a request selected by the ObjectSelector. +type Matcher struct { +} + +func matchObject(obj runtime.Object, selector labels.Selector) bool { + if obj == nil { + return false + } + accessor, err := meta.Accessor(obj) + if err != nil { + klog.V(5).Infof("cannot access metadata of %v: %v", obj, err) + return false + } + return selector.Matches(labels.Set(accessor.GetLabels())) + +} + +// MatchObjectSelector decideds whether the request matches the ObjectSelector +// of the webhook. Only when they match, the webhook is called. +func (m *Matcher) MatchObjectSelector(h webhook.WebhookAccessor, attr admission.Attributes) (bool, *apierrors.StatusError) { + // TODO: adding an LRU cache to cache the translation + selector, err := metav1.LabelSelectorAsSelector(h.GetObjectSelector()) + if err != nil { + return false, apierrors.NewInternalError(err) + } + if selector.Empty() { + return true, nil + } + return matchObject(attr.GetObject(), selector) || matchObject(attr.GetOldObject(), selector), nil +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher_test.go new file mode 100644 index 00000000000..823fabc9644 --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/object/matcher_test.go @@ -0,0 +1,130 @@ +/* +Copyright 2019 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 object + +import ( + "testing" + + "k8s.io/api/admissionregistration/v1beta1" + corev1 "k8s.io/api/core/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/apiserver/pkg/admission" + "k8s.io/apiserver/pkg/admission/plugin/webhook" +) + +func TestObjectSelector(t *testing.T) { + nodeLevel1 := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "runlevel": "1", + }, + }, + } + nodeLevel2 := &corev1.Node{ + ObjectMeta: metav1.ObjectMeta{ + Labels: map[string]string{ + "runlevel": "2", + }, + }, + } + runLevel1Excluder := &metav1.LabelSelector{ + MatchExpressions: []metav1.LabelSelectorRequirement{ + { + Key: "runlevel", + Operator: metav1.LabelSelectorOpNotIn, + Values: []string{"1"}, + }, + }, + } + matcher := &Matcher{} + allScopes := v1beta1.AllScopes + testcases := []struct { + name string + + objectSelector *metav1.LabelSelector + attrs admission.Attributes + + expectCall bool + }{ + { + name: "empty object selector matches everything", + objectSelector: &metav1.LabelSelector{}, + attrs: admission.NewAttributesRecord(nil, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectCall: true, + }, + { + name: "matches new object", + objectSelector: runLevel1Excluder, + attrs: admission.NewAttributesRecord(nodeLevel2, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectCall: true, + }, + { + name: "matches old object", + objectSelector: runLevel1Excluder, + attrs: admission.NewAttributesRecord(nil, nodeLevel2, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Delete, &metav1.DeleteOptions{}, false, nil), + expectCall: true, + }, + { + name: "does not match new object", + objectSelector: runLevel1Excluder, + attrs: admission.NewAttributesRecord(nodeLevel1, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectCall: false, + }, + { + name: "does not match old object", + objectSelector: runLevel1Excluder, + attrs: admission.NewAttributesRecord(nil, nodeLevel1, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectCall: false, + }, + { + name: "does not match object that does not implement Object interface", + objectSelector: runLevel1Excluder, + attrs: admission.NewAttributesRecord(&corev1.NodeProxyOptions{}, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectCall: false, + }, + { + name: "empty selector matches everything, including object that does not implement Object interface", + objectSelector: &metav1.LabelSelector{}, + attrs: admission.NewAttributesRecord(&corev1.NodeProxyOptions{}, nil, schema.GroupVersionKind{}, "", "name", schema.GroupVersionResource{}, "", admission.Create, &metav1.CreateOptions{}, false, nil), + expectCall: true, + }, + } + + for _, testcase := range testcases { + hook := &v1beta1.ValidatingWebhook{ + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: testcase.objectSelector, + Rules: []v1beta1.RuleWithOperations{{ + Operations: []v1beta1.OperationType{"*"}, + Rule: v1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*"}, Scope: &allScopes}, + }}} + + t.Run(testcase.name, func(t *testing.T) { + match, err := matcher.MatchObjectSelector(webhook.NewValidatingWebhookAccessor("mock-hook", hook), testcase.attrs) + if err != nil { + t.Error(err) + } + if testcase.expectCall && !match { + t.Errorf("expected the webhook to be called") + } + if !testcase.expectCall && match { + t.Errorf("expected the webhook to be called") + } + }) + } +} diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go index ee5467478ca..fd2d5a0d797 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/testcase.go @@ -49,8 +49,11 @@ var sideEffectsNone = registrationv1beta1.SideEffectClassNone var sideEffectsSome = registrationv1beta1.SideEffectClassSome var sideEffectsNoneOnDryRun = registrationv1beta1.SideEffectClassNoneOnDryRun -// NewFakeDataSource returns a mock client and informer returning the given webhooks. -func NewFakeDataSource(name string, webhooks []registrationv1beta1.Webhook, mutating bool, stopCh <-chan struct{}) (clientset kubernetes.Interface, factory informers.SharedInformerFactory) { +var reinvokeNever = registrationv1beta1.NeverReinvocationPolicy +var reinvokeIfNeeded = registrationv1beta1.IfNeededReinvocationPolicy + +// NewFakeValidatingDataSource returns a mock client and informer returning the given webhooks. +func NewFakeValidatingDataSource(name string, webhooks []registrationv1beta1.ValidatingWebhook, stopCh <-chan struct{}) (clientset kubernetes.Interface, factory informers.SharedInformerFactory) { var objs = []runtime.Object{ &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ @@ -61,21 +64,37 @@ func NewFakeDataSource(name string, webhooks []registrationv1beta1.Webhook, muta }, }, } - if mutating { - objs = append(objs, ®istrationv1beta1.MutatingWebhookConfiguration{ + objs = append(objs, ®istrationv1beta1.ValidatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-webhooks", + }, + Webhooks: webhooks, + }) + + client := fakeclientset.NewSimpleClientset(objs...) + informerFactory := informers.NewSharedInformerFactory(client, 0) + + return client, informerFactory +} + +// NewFakeMutatingDataSource returns a mock client and informer returning the given webhooks. +func NewFakeMutatingDataSource(name string, webhooks []registrationv1beta1.MutatingWebhook, stopCh <-chan struct{}) (clientset kubernetes.Interface, factory informers.SharedInformerFactory) { + var objs = []runtime.Object{ + &corev1.Namespace{ ObjectMeta: metav1.ObjectMeta{ - Name: "test-webhooks", + Name: name, + Labels: map[string]string{ + "runlevel": "0", + }, }, - Webhooks: webhooks, - }) - } else { - objs = append(objs, ®istrationv1beta1.ValidatingWebhookConfiguration{ - ObjectMeta: metav1.ObjectMeta{ - Name: "test-webhooks", - }, - Webhooks: webhooks, - }) + }, } + objs = append(objs, ®istrationv1beta1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{ + Name: "test-webhooks", + }, + Webhooks: webhooks, + }) client := fakeclientset.NewSimpleClientset(objs...) informerFactory := informers.NewSharedInformerFactory(client, 0) @@ -181,51 +200,88 @@ func (c urlConfigGenerator) ccfgURL(urlPath string) registrationv1beta1.WebhookC } } -// Test is a webhook test case. -type Test struct { - Name string - Webhooks []registrationv1beta1.Webhook - Path string - IsCRD bool - IsDryRun bool - AdditionalLabels map[string]string - ExpectLabels map[string]string - ExpectAllow bool - ErrorContains string - ExpectAnnotations map[string]string - ExpectStatusCode int32 +// ValidatingTest is a validating webhook test case. +type ValidatingTest struct { + Name string + Webhooks []registrationv1beta1.ValidatingWebhook + Path string + IsCRD bool + IsDryRun bool + AdditionalLabels map[string]string + ExpectLabels map[string]string + ExpectAllow bool + ErrorContains string + ExpectAnnotations map[string]string + ExpectStatusCode int32 + ExpectReinvokeWebhooks map[string]bool +} + +// MutatingTest is a mutating webhook test case. +type MutatingTest struct { + Name string + Webhooks []registrationv1beta1.MutatingWebhook + Path string + IsCRD bool + IsDryRun bool + AdditionalLabels map[string]string + ExpectLabels map[string]string + ExpectAllow bool + ErrorContains string + ExpectAnnotations map[string]string + ExpectStatusCode int32 + ExpectReinvokeWebhooks map[string]bool +} + +// ConvertToMutatingTestCases converts a validating test case to a mutating one for test purposes. +func ConvertToMutatingTestCases(tests []ValidatingTest) []MutatingTest { + r := make([]MutatingTest, len(tests)) + for i, t := range tests { + r[i] = MutatingTest{t.Name, ConvertToMutatingWebhooks(t.Webhooks), t.Path, t.IsCRD, t.IsDryRun, t.AdditionalLabels, t.ExpectLabels, t.ExpectAllow, t.ErrorContains, t.ExpectAnnotations, t.ExpectStatusCode, t.ExpectReinvokeWebhooks} + } + return r +} + +// ConvertToMutatingWebhooks converts a validating webhook to a mutating one for test purposes. +func ConvertToMutatingWebhooks(webhooks []registrationv1beta1.ValidatingWebhook) []registrationv1beta1.MutatingWebhook { + mutating := make([]registrationv1beta1.MutatingWebhook, len(webhooks)) + for i, h := range webhooks { + mutating[i] = registrationv1beta1.MutatingWebhook{h.Name, h.ClientConfig, h.Rules, h.FailurePolicy, h.MatchPolicy, h.NamespaceSelector, h.ObjectSelector, h.SideEffects, h.TimeoutSeconds, h.AdmissionReviewVersions, nil} + } + return mutating } // NewNonMutatingTestCases returns test cases with a given base url. // All test cases in NewNonMutatingTestCases have no Patch set in // AdmissionResponse. The test cases are used by both MutatingAdmissionWebhook // and ValidatingAdmissionWebhook. -func NewNonMutatingTestCases(url *url.URL) []Test { +func NewNonMutatingTestCases(url *url.URL) []ValidatingTest { policyFail := registrationv1beta1.Fail policyIgnore := registrationv1beta1.Ignore ccfgURL := urlConfigGenerator{url}.ccfgURL - return []Test{ + return []ValidatingTest{ { Name: "no match", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "nomatch", ClientConfig: ccfgSVC("disallow"), Rules: []registrationv1beta1.RuleWithOperations{{ Operations: []registrationv1beta1.OperationType{registrationv1beta1.Create}, }}, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectAllow: true, }, { Name: "match & allow", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "allow.example.com", ClientConfig: ccfgSVC("allow"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectAllow: true, @@ -233,11 +289,12 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match & disallow", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "disallow", ClientConfig: ccfgSVC("disallow"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectStatusCode: http.StatusForbidden, @@ -245,11 +302,12 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match & disallow ii", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "disallowReason", ClientConfig: ccfgSVC("disallowReason"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectStatusCode: http.StatusForbidden, @@ -257,7 +315,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match & disallow & but allowed because namespaceSelector exempt the ns", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "disallow", ClientConfig: ccfgSVC("disallow"), Rules: newMatchEverythingRules(), @@ -268,6 +326,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test { Operator: metav1.LabelSelectorOpIn, }}, }, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -275,7 +334,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match & disallow & but allowed because namespaceSelector exempt the ns ii", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "disallow", ClientConfig: ccfgSVC("disallow"), Rules: newMatchEverythingRules(), @@ -286,17 +345,19 @@ func NewNonMutatingTestCases(url *url.URL) []Test { Operator: metav1.LabelSelectorOpNotIn, }}, }, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectAllow: true, }, { Name: "match & fail (but allow because fail open)", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "internalErr A", ClientConfig: ccfgSVC("internalErr"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyIgnore, AdmissionReviewVersions: []string{"v1beta1"}, }, { @@ -304,6 +365,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test { ClientConfig: ccfgSVC("internalErr"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyIgnore, AdmissionReviewVersions: []string{"v1beta1"}, }, { @@ -311,6 +373,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test { ClientConfig: ccfgSVC("internalErr"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyIgnore, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -319,22 +382,25 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match & fail (but disallow because fail close on nil FailurePolicy)", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "internalErr A", ClientConfig: ccfgSVC("internalErr"), NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: matchEverythingRules, AdmissionReviewVersions: []string{"v1beta1"}, }, { Name: "internalErr B", ClientConfig: ccfgSVC("internalErr"), NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: matchEverythingRules, AdmissionReviewVersions: []string{"v1beta1"}, }, { Name: "internalErr C", ClientConfig: ccfgSVC("internalErr"), NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, Rules: matchEverythingRules, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -343,11 +409,12 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match & fail (but fail because fail closed)", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "internalErr A", ClientConfig: ccfgSVC("internalErr"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyFail, AdmissionReviewVersions: []string{"v1beta1"}, }, { @@ -355,6 +422,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test { ClientConfig: ccfgSVC("internalErr"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyFail, AdmissionReviewVersions: []string{"v1beta1"}, }, { @@ -362,6 +430,7 @@ func NewNonMutatingTestCases(url *url.URL) []Test { ClientConfig: ccfgSVC("internalErr"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyFail, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -370,11 +439,12 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match & allow (url)", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "allow.example.com", ClientConfig: ccfgURL("allow"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectAllow: true, @@ -382,35 +452,38 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match & disallow (url)", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "disallow", ClientConfig: ccfgURL("disallow"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectStatusCode: http.StatusForbidden, ErrorContains: "without explanation", }, { Name: "absent response and fail open", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "nilResponse", ClientConfig: ccfgURL("nilResponse"), FailurePolicy: &policyIgnore, Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectAllow: true, }, { Name: "absent response and fail closed", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "nilResponse", ClientConfig: ccfgURL("nilResponse"), FailurePolicy: &policyFail, Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectStatusCode: http.StatusInternalServerError, @@ -418,13 +491,14 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "no match dry run", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "nomatch", ClientConfig: ccfgSVC("allow"), Rules: []registrationv1beta1.RuleWithOperations{{ Operations: []registrationv1beta1.OperationType{registrationv1beta1.Create}, }}, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, SideEffects: &sideEffectsSome, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -433,11 +507,12 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match dry run side effects Unknown", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "allow", ClientConfig: ccfgSVC("allow"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, SideEffects: &sideEffectsUnknown, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -447,11 +522,12 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match dry run side effects None", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "allow", ClientConfig: ccfgSVC("allow"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, SideEffects: &sideEffectsNone, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -461,11 +537,12 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match dry run side effects Some", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "allow", ClientConfig: ccfgSVC("allow"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, SideEffects: &sideEffectsSome, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -475,11 +552,12 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "match dry run side effects NoneOnDryRun", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "allow", ClientConfig: ccfgSVC("allow"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, SideEffects: &sideEffectsNoneOnDryRun, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -489,15 +567,40 @@ func NewNonMutatingTestCases(url *url.URL) []Test { }, { Name: "illegal annotation format", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "invalidAnnotation", ClientConfig: ccfgURL("invalidAnnotation"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectAllow: true, }, + { + Name: "skip webhook whose objectSelector does not match", + Webhooks: []registrationv1beta1.ValidatingWebhook{{ + Name: "allow.example.com", + ClientConfig: ccfgSVC("allow"), + Rules: matchEverythingRules, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + AdmissionReviewVersions: []string{"v1beta1"}, + }, { + Name: "shouldNotBeCalled", + ClientConfig: ccfgSVC("shouldNotBeCalled"), + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "label": "nonexistent", + }, + }, + Rules: matchEverythingRules, + AdmissionReviewVersions: []string{"v1beta1"}, + }}, + ExpectAllow: true, + ExpectAnnotations: map[string]string{"allow.example.com/key1": "value1"}, + }, // No need to test everything with the url case, since only the // connection is different. } @@ -506,15 +609,16 @@ func NewNonMutatingTestCases(url *url.URL) []Test { // NewMutatingTestCases returns test cases with a given base url. // All test cases in NewMutatingTestCases have Patch set in // AdmissionResponse. The test cases are only used by both MutatingAdmissionWebhook. -func NewMutatingTestCases(url *url.URL) []Test { - return []Test{ +func NewMutatingTestCases(url *url.URL) []MutatingTest { + return []MutatingTest{ { Name: "match & remove label", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.MutatingWebhook{{ Name: "removelabel.example.com", ClientConfig: ccfgSVC("removeLabel"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectAllow: true, @@ -524,11 +628,12 @@ func NewMutatingTestCases(url *url.URL) []Test { }, { Name: "match & add label", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.MutatingWebhook{{ Name: "addLabel", ClientConfig: ccfgSVC("addLabel"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectAllow: true, @@ -536,11 +641,12 @@ func NewMutatingTestCases(url *url.URL) []Test { }, { Name: "match CRD & add label", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.MutatingWebhook{{ Name: "addLabel", ClientConfig: ccfgSVC("addLabel"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, IsCRD: true, @@ -549,11 +655,12 @@ func NewMutatingTestCases(url *url.URL) []Test { }, { Name: "match CRD & remove label", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.MutatingWebhook{{ Name: "removelabel.example.com", ClientConfig: ccfgSVC("removeLabel"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, IsCRD: true, @@ -564,11 +671,12 @@ func NewMutatingTestCases(url *url.URL) []Test { }, { Name: "match & invalid mutation", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.MutatingWebhook{{ Name: "invalidMutation", ClientConfig: ccfgSVC("invalidMutation"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, AdmissionReviewVersions: []string{"v1beta1"}, }}, ExpectStatusCode: http.StatusInternalServerError, @@ -576,11 +684,12 @@ func NewMutatingTestCases(url *url.URL) []Test { }, { Name: "match & remove label dry run unsupported", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.MutatingWebhook{{ Name: "removeLabel", ClientConfig: ccfgSVC("removeLabel"), Rules: matchEverythingRules, NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, SideEffects: &sideEffectsUnknown, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -588,15 +697,107 @@ func NewMutatingTestCases(url *url.URL) []Test { ExpectStatusCode: http.StatusBadRequest, ErrorContains: "does not support dry run", }, + { + Name: "first webhook remove labels, second webhook shouldn't be called", + Webhooks: []registrationv1beta1.MutatingWebhook{{ + Name: "removelabel.example.com", + ClientConfig: ccfgSVC("removeLabel"), + Rules: matchEverythingRules, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "remove": "me", + }, + }, + AdmissionReviewVersions: []string{"v1beta1"}, + }, { + Name: "shouldNotBeCalled", + ClientConfig: ccfgSVC("shouldNotBeCalled"), + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{ + MatchLabels: map[string]string{ + "remove": "me", + }, + }, + Rules: matchEverythingRules, + AdmissionReviewVersions: []string{"v1beta1"}, + }}, + ExpectAllow: true, + AdditionalLabels: map[string]string{"remove": "me"}, + ExpectLabels: map[string]string{"pod.name": "my-pod"}, + ExpectAnnotations: map[string]string{"removelabel.example.com/key1": "value1"}, + }, // No need to test everything with the url case, since only the // connection is different. + { + Name: "match & reinvoke if needed policy", + Webhooks: []registrationv1beta1.MutatingWebhook{{ + Name: "addLabel", + ClientConfig: ccfgSVC("addLabel"), + Rules: matchEverythingRules, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + AdmissionReviewVersions: []string{"v1beta1"}, + ReinvocationPolicy: &reinvokeIfNeeded, + }, { + Name: "removeLabel", + ClientConfig: ccfgSVC("removeLabel"), + Rules: matchEverythingRules, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + AdmissionReviewVersions: []string{"v1beta1"}, + ReinvocationPolicy: &reinvokeIfNeeded, + }}, + AdditionalLabels: map[string]string{"remove": "me"}, + ExpectAllow: true, + ExpectReinvokeWebhooks: map[string]bool{"addLabel": true}, + }, + { + Name: "match & never reinvoke policy", + Webhooks: []registrationv1beta1.MutatingWebhook{{ + Name: "addLabel", + ClientConfig: ccfgSVC("addLabel"), + Rules: matchEverythingRules, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + AdmissionReviewVersions: []string{"v1beta1"}, + ReinvocationPolicy: &reinvokeNever, + }}, + ExpectAllow: true, + ExpectReinvokeWebhooks: map[string]bool{"addLabel": false}, + }, + { + Name: "match & never reinvoke policy (by default)", + Webhooks: []registrationv1beta1.MutatingWebhook{{ + Name: "addLabel", + ClientConfig: ccfgSVC("addLabel"), + Rules: matchEverythingRules, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + AdmissionReviewVersions: []string{"v1beta1"}, + }}, + ExpectAllow: true, + ExpectReinvokeWebhooks: map[string]bool{"addLabel": false}, + }, + { + Name: "match & no reinvoke", + Webhooks: []registrationv1beta1.MutatingWebhook{{ + Name: "noop", + ClientConfig: ccfgSVC("noop"), + Rules: matchEverythingRules, + NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, + AdmissionReviewVersions: []string{"v1beta1"}, + }}, + ExpectAllow: true, + }, } } // CachedTest is a test case for the client manager. type CachedTest struct { Name string - Webhooks []registrationv1beta1.Webhook + Webhooks []registrationv1beta1.ValidatingWebhook ExpectAllow bool ExpectCacheMiss bool } @@ -609,11 +810,12 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest { return []CachedTest{ { Name: "uncached: service webhook, path 'allow'", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "cache1", ClientConfig: ccfgSVC("allow"), Rules: newMatchEverythingRules(), NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyIgnore, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -622,11 +824,12 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest { }, { Name: "uncached: service webhook, path 'internalErr'", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "cache2", ClientConfig: ccfgSVC("internalErr"), Rules: newMatchEverythingRules(), NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyIgnore, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -635,11 +838,12 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest { }, { Name: "cached: service webhook, path 'allow'", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "cache3", ClientConfig: ccfgSVC("allow"), Rules: newMatchEverythingRules(), NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyIgnore, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -648,11 +852,12 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest { }, { Name: "uncached: url webhook, path 'allow'", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "cache4", ClientConfig: ccfgURL("allow"), Rules: newMatchEverythingRules(), NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyIgnore, AdmissionReviewVersions: []string{"v1beta1"}, }}, @@ -661,11 +866,12 @@ func NewCachedClientTestcases(url *url.URL) []CachedTest { }, { Name: "cached: service webhook, path 'allow'", - Webhooks: []registrationv1beta1.Webhook{{ + Webhooks: []registrationv1beta1.ValidatingWebhook{{ Name: "cache5", ClientConfig: ccfgURL("allow"), Rules: newMatchEverythingRules(), NamespaceSelector: &metav1.LabelSelector{}, + ObjectSelector: &metav1.LabelSelector{}, FailurePolicy: &policyIgnore, AdmissionReviewVersions: []string{"v1beta1"}, }}, diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/webhook_server.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/webhook_server.go index 5d080745dca..6a9ee5ff144 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/webhook_server.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/testing/webhook_server.go @@ -82,6 +82,17 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) { }, }, }) + case "/shouldNotBeCalled": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: false, + Result: &metav1.Status{ + Message: "doesn't expect labels to match object selector", + Code: http.StatusForbidden, + }, + }, + }) case "/allow": w.Header().Set("Content-Type", "application/json") json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ @@ -138,6 +149,13 @@ func webhookHandler(w http.ResponseWriter, r *http.Request) { }, }, }) + case "/noop": + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: true, + }, + }) default: http.NotFound(w, r) } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util/BUILD index dd9ed4916d0..3a9b036f76e 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util/BUILD @@ -7,7 +7,7 @@ go_library( importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/util", visibility = ["//visibility:public"], deps = [ - "//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", "//staging/src/k8s.io/apiserver/pkg/util/webhook:go_default_library", ], ) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go index 8f489639aaa..b9907286fc5 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/util/client_config.go @@ -17,38 +17,39 @@ limitations under the License. package util import ( - "k8s.io/api/admissionregistration/v1beta1" - "k8s.io/apiserver/pkg/util/webhook" + "k8s.io/apiserver/pkg/admission/plugin/webhook" + webhookutil "k8s.io/apiserver/pkg/util/webhook" ) -// HookClientConfigForWebhook construct a webhook.ClientConfig using a v1beta1.Webhook API object. -// webhook.ClientConfig is used to create a HookClient and the purpose of the config struct is to -// share that with other packages that need to create a HookClient. -func HookClientConfigForWebhook(w *v1beta1.Webhook) webhook.ClientConfig { - ret := webhook.ClientConfig{Name: w.Name, CABundle: w.ClientConfig.CABundle} - if w.ClientConfig.URL != nil { - ret.URL = *w.ClientConfig.URL +// HookClientConfigForWebhook construct a webhookutil.ClientConfig using a WebhookAccessor to access +// v1beta1.MutatingWebhook and v1beta1.ValidatingWebhook API objects. webhookutil.ClientConfig is used +// to create a HookClient and the purpose of the config struct is to share that with other packages +// that need to create a HookClient. +func HookClientConfigForWebhook(w webhook.WebhookAccessor) webhookutil.ClientConfig { + ret := webhookutil.ClientConfig{Name: w.GetName(), CABundle: w.GetClientConfig().CABundle} + if w.GetClientConfig().URL != nil { + ret.URL = *w.GetClientConfig().URL } - if w.ClientConfig.Service != nil { - ret.Service = &webhook.ClientConfigService{ - Name: w.ClientConfig.Service.Name, - Namespace: w.ClientConfig.Service.Namespace, + if w.GetClientConfig().Service != nil { + ret.Service = &webhookutil.ClientConfigService{ + Name: w.GetClientConfig().Service.Name, + Namespace: w.GetClientConfig().Service.Namespace, } - if w.ClientConfig.Service.Port != nil { - ret.Service.Port = *w.ClientConfig.Service.Port + if w.GetClientConfig().Service.Port != nil { + ret.Service.Port = *w.GetClientConfig().Service.Port } else { ret.Service.Port = 443 } - if w.ClientConfig.Service.Path != nil { - ret.Service.Path = *w.ClientConfig.Service.Path + if w.GetClientConfig().Service.Path != nil { + ret.Service.Path = *w.GetClientConfig().Service.Path } } return ret } // HasAdmissionReviewVersion check whether a version is accepted by a given webhook. -func HasAdmissionReviewVersion(a string, w *v1beta1.Webhook) bool { - for _, b := range w.AdmissionReviewVersions { +func HasAdmissionReviewVersion(a string, w webhook.WebhookAccessor) bool { + for _, b := range w.GetAdmissionReviewVersions() { if b == a { return true } diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/BUILD b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/BUILD index b005e484bd7..c0147a54753 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/BUILD +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/BUILD @@ -19,6 +19,7 @@ go_library( "//staging/src/k8s.io/apiserver/pkg/admission:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/configuration:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/metrics:go_default_library", + "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/errors:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:go_default_library", "//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request:go_default_library", diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go index d7421e2b53b..dda52c90e57 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/dispatcher.go @@ -22,8 +22,6 @@ import ( "sync" "time" - "k8s.io/klog" - admissionv1beta1 "k8s.io/api/admission/v1beta1" "k8s.io/api/admissionregistration/v1beta1" apierrors "k8s.io/apimachinery/pkg/api/errors" @@ -31,36 +29,55 @@ import ( utilruntime "k8s.io/apimachinery/pkg/util/runtime" "k8s.io/apiserver/pkg/admission" admissionmetrics "k8s.io/apiserver/pkg/admission/metrics" + "k8s.io/apiserver/pkg/admission/plugin/webhook" webhookerrors "k8s.io/apiserver/pkg/admission/plugin/webhook/errors" "k8s.io/apiserver/pkg/admission/plugin/webhook/generic" "k8s.io/apiserver/pkg/admission/plugin/webhook/request" "k8s.io/apiserver/pkg/admission/plugin/webhook/util" - "k8s.io/apiserver/pkg/util/webhook" + webhookutil "k8s.io/apiserver/pkg/util/webhook" + "k8s.io/klog" ) type validatingDispatcher struct { - cm *webhook.ClientManager + cm *webhookutil.ClientManager + plugin *Plugin } -func newValidatingDispatcher(cm *webhook.ClientManager) generic.Dispatcher { - return &validatingDispatcher{cm} +func newValidatingDispatcher(p *Plugin) func(cm *webhookutil.ClientManager) generic.Dispatcher { + return func(cm *webhookutil.ClientManager) generic.Dispatcher { + return &validatingDispatcher{cm, p} + } } var _ generic.Dispatcher = &validatingDispatcher{} -func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attributes, o admission.ObjectInterfaces, relevantHooks []*generic.WebhookInvocation) error { +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]*generic.VersionedAttributes{} - for _, call := range relevantHooks { - // If we already have this version, continue - if _, ok := versionedAttrs[call.Kind]; ok { + for _, hook := range hooks { + invocation, statusError := d.plugin.ShouldCallHook(hook, attr, o) + if statusError != nil { + return statusError + } + if invocation == nil { continue } - versionedAttr, err := generic.NewVersionedAttributes(attr, call.Kind, o) + relevantHooks = append(relevantHooks, invocation) + // If we already have this version, continue + if _, ok := versionedAttrs[invocation.Kind]; ok { + continue + } + versionedAttr, err := generic.NewVersionedAttributes(attr, invocation.Kind, o) if err != nil { return apierrors.NewInternalError(err) } - versionedAttrs[call.Kind] = versionedAttr + versionedAttrs[invocation.Kind] = versionedAttr + } + + if len(relevantHooks) == 0 { + // no matching hooks + return nil } wg := sync.WaitGroup{} @@ -69,18 +86,21 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attr for i := range relevantHooks { go func(invocation *generic.WebhookInvocation) { defer wg.Done() - hook := invocation.Webhook + hook, ok := invocation.Webhook.GetValidatingWebhook() + if !ok { + utilruntime.HandleError(fmt.Errorf("validating webhook dispatch requires v1beta1.ValidatingWebhook, but got %T", hook)) + return + } versionedAttr := versionedAttrs[invocation.Kind] - t := time.Now() - err := d.callHook(ctx, invocation, versionedAttr) + err := d.callHook(ctx, hook, invocation, versionedAttr) admissionmetrics.Metrics.ObserveWebhook(time.Since(t), err != nil, versionedAttr.Attributes, "validating", hook.Name) if err == nil { return } ignoreClientCallFailures := hook.FailurePolicy != nil && *hook.FailurePolicy == v1beta1.Ignore - if callErr, ok := err.(*webhook.ErrCallingWebhook); ok { + if callErr, ok := err.(*webhookutil.ErrCallingWebhook); ok { if ignoreClientCallFailures { klog.Warningf("Failed calling webhook, failing open %v: %v", hook.Name, callErr) utilruntime.HandleError(callErr) @@ -115,11 +135,10 @@ func (d *validatingDispatcher) Dispatch(ctx context.Context, attr admission.Attr return errs[0] } -func (d *validatingDispatcher) callHook(ctx context.Context, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes) error { - h := invocation.Webhook +func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.ValidatingWebhook, invocation *generic.WebhookInvocation, attr *generic.VersionedAttributes) error { if attr.Attributes.IsDryRun() { if h.SideEffects == nil { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} + return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook SideEffects is nil")} } if !(*h.SideEffects == v1beta1.SideEffectClassNone || *h.SideEffects == v1beta1.SideEffectClassNoneOnDryRun) { return webhookerrors.NewDryRunUnsupportedErr(h.Name) @@ -128,15 +147,15 @@ func (d *validatingDispatcher) callHook(ctx context.Context, invocation *generic // Currently dispatcher only supports `v1beta1` AdmissionReview // TODO: Make the dispatcher capable of sending multiple AdmissionReview versions - if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, h) { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")} + if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, invocation.Webhook) { + return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")} } // Make the webhook request request := request.CreateAdmissionReview(attr, invocation) - client, err := d.cm.HookClient(util.HookClientConfigForWebhook(h)) + client, err := d.cm.HookClient(util.HookClientConfigForWebhook(invocation.Webhook)) if err != nil { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} + return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err} } response := &admissionv1beta1.AdmissionReview{} r := client.Post().Context(ctx).Body(&request) @@ -144,11 +163,11 @@ func (d *validatingDispatcher) callHook(ctx context.Context, invocation *generic r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second) } if err := r.Do().Into(response); err != nil { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: err} + return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err} } if response.Response == nil { - return &webhook.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} + return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")} } for k, v := range response.Response.AuditAnnotations { key := h.Name + "/" + k diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go index 388a237c984..30e5c9d3319 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin.go @@ -51,11 +51,13 @@ var _ admission.ValidationInterface = &Plugin{} // NewValidatingAdmissionWebhook returns a generic admission webhook plugin. func NewValidatingAdmissionWebhook(configFile io.Reader) (*Plugin, error) { handler := admission.NewHandler(admission.Connect, admission.Create, admission.Delete, admission.Update) - webhook, err := generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher) + p := &Plugin{} + var err error + p.Webhook, err = generic.NewWebhook(handler, configFile, configuration.NewValidatingWebhookConfigurationManager, newValidatingDispatcher(p)) if err != nil { return nil, err } - return &Plugin{webhook}, nil + return p, nil } // Validate makes an admission decision based on the request attributes. diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go index 0ed2a9744c3..a2aec191e39 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/validating/plugin_test.go @@ -51,7 +51,7 @@ func TestValidate(t *testing.T) { } ns := "webhook-test" - client, informer := webhooktesting.NewFakeDataSource(ns, tt.Webhooks, false, stopCh) + client, informer := webhooktesting.NewFakeValidatingDataSource(ns, tt.Webhooks, stopCh) wh.SetAuthenticationInfoResolverWrapper(webhooktesting.Wrapper(webhooktesting.NewAuthenticationInfoResolver(new(int32)))) wh.SetServiceResolver(webhooktesting.NewServiceResolver(*serverURL)) @@ -116,7 +116,7 @@ func TestValidateCachedClient(t *testing.T) { for _, tt := range webhooktesting.NewCachedClientTestcases(serverURL) { ns := "webhook-test" - client, informer := webhooktesting.NewFakeDataSource(ns, tt.Webhooks, false, stopCh) + client, informer := webhooktesting.NewFakeValidatingDataSource(ns, tt.Webhooks, stopCh) // override the webhook source. The client cache will stay the same. cacheMisses := new(int32) diff --git a/staging/src/k8s.io/apiserver/pkg/admission/plugins.go b/staging/src/k8s.io/apiserver/pkg/admission/plugins.go index bdf087e564f..d37af509c66 100644 --- a/staging/src/k8s.io/apiserver/pkg/admission/plugins.go +++ b/staging/src/k8s.io/apiserver/pkg/admission/plugins.go @@ -160,7 +160,7 @@ func (ps *Plugins) NewFromPlugins(pluginNames []string, configProvider ConfigPro if len(validationPlugins) != 0 { klog.Infof("Loaded %d validating admission controller(s) successfully in the following order: %s.", len(validationPlugins), strings.Join(validationPlugins, ",")) } - return chainAdmissionHandler(handlers), nil + return newReinvocationHandler(chainAdmissionHandler(handlers)), nil } // InitPlugin creates an instance of the named interface. diff --git a/staging/src/k8s.io/apiserver/pkg/admission/reinvocation.go b/staging/src/k8s.io/apiserver/pkg/admission/reinvocation.go new file mode 100644 index 00000000000..b99e604e05c --- /dev/null +++ b/staging/src/k8s.io/apiserver/pkg/admission/reinvocation.go @@ -0,0 +1,62 @@ +/* +Copyright 2019 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 admission + +// newReinvocationHandler creates a handler that wraps the provided admission chain and reinvokes it +// if needed according to re-invocation policy of the webhooks. +func newReinvocationHandler(admissionChain Interface) Interface { + return &reinvoker{admissionChain} +} + +type reinvoker struct { + admissionChain Interface +} + +// Admit performs an admission control check using the wrapped admission chain, reinvoking the +// admission chain if needed according to the reinvocation policy. Plugins are expected to check +// the admission attributes' reinvocation context against their reinvocation policy to decide if +// they should re-run, and to update the reinvocation context if they perform any mutations. +func (r *reinvoker) Admit(a Attributes, o ObjectInterfaces) error { + if mutator, ok := r.admissionChain.(MutationInterface); ok { + err := mutator.Admit(a, o) + if err != nil { + return err + } + s := a.GetReinvocationContext() + if s.ShouldReinvoke() { + s.SetIsReinvoke() + // Calling admit a second time will reinvoke all in-tree plugins + // as well as any webhook plugins that need to be reinvoked based on the + // reinvocation policy. + return mutator.Admit(a, o) + } + } + return nil +} + +// Validate performs an admission control check using the wrapped admission chain, and returns immediately on first error. +func (r *reinvoker) Validate(a Attributes, o ObjectInterfaces) error { + if validator, ok := r.admissionChain.(ValidationInterface); ok { + return validator.Validate(a, o) + } + return nil +} + +// Handles will return true if any of the admission chain handlers handle the given operation. +func (r *reinvoker) Handles(operation Operation) bool { + return r.admissionChain.Handles(operation) +} diff --git a/test/e2e/apimachinery/webhook.go b/test/e2e/apimachinery/webhook.go index 2c4ba66379b..00e7731f891 100644 --- a/test/e2e/apimachinery/webhook.go +++ b/test/e2e/apimachinery/webhook.go @@ -436,7 +436,7 @@ func registerWebhook(f *framework.Framework, context *certContext) func() { ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.ValidatingWebhook{ { Name: "deny-unwanted-pod-container-name-and-label.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -517,7 +517,7 @@ func registerWebhookForAttachingPod(f *framework.Framework, context *certContext ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.ValidatingWebhook{ { Name: "deny-attaching-pod.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -561,7 +561,7 @@ func registerMutatingWebhookForConfigMap(f *framework.Framework, context *certCo ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.MutatingWebhook{ { Name: "adding-configmap-data-stage-1.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -638,7 +638,7 @@ func registerMutatingWebhookForPod(f *framework.Framework, context *certContext) ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.MutatingWebhook{ { Name: "adding-init-container.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -841,8 +841,8 @@ func testAttachingPodWebhook(f *framework.Framework) { // failingWebhook returns a webhook with rule of create configmaps, // but with an invalid client config so that server cannot communicate with it -func failingWebhook(namespace, name string) v1beta1.Webhook { - return v1beta1.Webhook{ +func failingWebhook(namespace, name string) v1beta1.ValidatingWebhook { + return v1beta1.ValidatingWebhook{ Name: name, Rules: []v1beta1.RuleWithOperations{{ Operations: []v1beta1.OperationType{v1beta1.Create}, @@ -889,7 +889,7 @@ func registerFailClosedWebhook(f *framework.Framework, context *certContext) fun ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.ValidatingWebhook{ // Server cannot talk to this webhook, so it always fails. // Because this webhook is configured fail-closed, request should be rejected after the call fails. hook, @@ -945,7 +945,7 @@ func registerValidatingWebhookForWebhookConfigurations(f *framework.Framework, c ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.ValidatingWebhook{ { Name: "deny-webhook-configuration-deletions.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -998,7 +998,7 @@ func registerMutatingWebhookForWebhookConfigurations(f *framework.Framework, con ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.MutatingWebhook{ { Name: "add-label-to-webhook-configurations.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -1050,7 +1050,7 @@ func testWebhooksForWebhookConfigurations(f *framework.Framework) { ObjectMeta: metav1.ObjectMeta{ Name: dummyValidatingWebhookConfigName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.ValidatingWebhook{ { Name: "dummy-validating-webhook.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -1098,7 +1098,7 @@ func testWebhooksForWebhookConfigurations(f *framework.Framework) { ObjectMeta: metav1.ObjectMeta{ Name: dummyMutatingWebhookConfigName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.MutatingWebhook{ { Name: "dummy-mutating-webhook.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -1306,7 +1306,7 @@ func registerWebhookForCustomResource(f *framework.Framework, context *certConte ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.ValidatingWebhook{ { Name: "deny-unwanted-custom-resource-data.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -1348,7 +1348,7 @@ func registerMutatingWebhookForCustomResource(f *framework.Framework, context *c ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.MutatingWebhook{ { Name: "mutate-custom-resource-data-stage-1.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -1543,7 +1543,7 @@ func registerValidatingWebhookForCRD(f *framework.Framework, context *certContex ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.ValidatingWebhook{ { Name: "deny-crd-with-unwanted-label.k8s.io", Rules: []v1beta1.RuleWithOperations{{ @@ -1649,7 +1649,7 @@ func registerSlowWebhook(f *framework.Framework, context *certContext, policy *v ObjectMeta: metav1.ObjectMeta{ Name: configName, }, - Webhooks: []v1beta1.Webhook{ + Webhooks: []v1beta1.ValidatingWebhook{ { Name: "allow-configmap-with-delay-webhook.k8s.io", Rules: []v1beta1.RuleWithOperations{{ diff --git a/test/integration/apiserver/admissionwebhook/BUILD b/test/integration/apiserver/admissionwebhook/BUILD index 3804acaf435..529a1c848c9 100644 --- a/test/integration/apiserver/admissionwebhook/BUILD +++ b/test/integration/apiserver/admissionwebhook/BUILD @@ -6,6 +6,7 @@ go_test( "admission_test.go", "broken_webhook_test.go", "main_test.go", + "reinvocation_test.go", ], rundir = ".", tags = [ @@ -21,6 +22,7 @@ go_test( "//staging/src/k8s.io/api/core/v1:go_default_library", "//staging/src/k8s.io/api/extensions/v1beta1:go_default_library", "//staging/src/k8s.io/api/policy/v1beta1:go_default_library", + "//staging/src/k8s.io/api/scheduling/v1:go_default_library", "//staging/src/k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", diff --git a/test/integration/apiserver/admissionwebhook/admission_test.go b/test/integration/apiserver/admissionwebhook/admission_test.go index c39ec915abf..b243f5a24df 100644 --- a/test/integration/apiserver/admissionwebhook/admission_test.go +++ b/test/integration/apiserver/admissionwebhook/admission_test.go @@ -283,6 +283,9 @@ func (h *holder) record(phase string, converted bool, request *v1beta1.Admission return } + if debug { + h.t.Logf("recording: %#v = %s %#v %v", webhookOptions{phase: phase, converted: converted}, request.Operation, request.Resource, request.SubResource) + } h.recorded[webhookOptions{phase: phase, converted: converted}] = request } @@ -1287,7 +1290,7 @@ func createV1beta1ValidationWebhook(client clientset.Interface, endpoint, conver // Attaching Admission webhook to API server _, err := client.AdmissionregistrationV1beta1().ValidatingWebhookConfigurations().Create(&admissionv1beta1.ValidatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: "admission.integration.test"}, - Webhooks: []admissionv1beta1.Webhook{ + Webhooks: []admissionv1beta1.ValidatingWebhook{ { Name: "admission.integration.test", ClientConfig: admissionv1beta1.WebhookClientConfig{ @@ -1323,7 +1326,7 @@ func createV1beta1MutationWebhook(client clientset.Interface, endpoint, converte // Attaching Mutation webhook to API server _, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: "mutation.integration.test"}, - Webhooks: []admissionv1beta1.Webhook{ + Webhooks: []admissionv1beta1.MutatingWebhook{ { Name: "mutation.integration.test", ClientConfig: admissionv1beta1.WebhookClientConfig{ diff --git a/test/integration/apiserver/admissionwebhook/broken_webhook_test.go b/test/integration/apiserver/admissionwebhook/broken_webhook_test.go index 7a08d4cd4da..061bd07d590 100644 --- a/test/integration/apiserver/admissionwebhook/broken_webhook_test.go +++ b/test/integration/apiserver/admissionwebhook/broken_webhook_test.go @@ -155,7 +155,7 @@ func brokenWebhookConfig(name string) *admissionregistrationv1beta1.ValidatingWe ObjectMeta: metav1.ObjectMeta{ Name: name, }, - Webhooks: []admissionregistrationv1beta1.Webhook{ + Webhooks: []admissionregistrationv1beta1.ValidatingWebhook{ { Name: "broken-webhook.k8s.io", Rules: []admissionregistrationv1beta1.RuleWithOperations{{ diff --git a/test/integration/apiserver/admissionwebhook/reinvocation_test.go b/test/integration/apiserver/admissionwebhook/reinvocation_test.go new file mode 100644 index 00000000000..417f616eca1 --- /dev/null +++ b/test/integration/apiserver/admissionwebhook/reinvocation_test.go @@ -0,0 +1,401 @@ +/* +Copyright 2019 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 ( + "crypto/tls" + "crypto/x509" + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "net/http/httptest" + "reflect" + "strings" + "sync" + "testing" + + "k8s.io/api/admission/v1beta1" + admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1" + registrationv1beta1 "k8s.io/api/admissionregistration/v1beta1" + corev1 "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" + schedulingv1 "k8s.io/api/scheduling/v1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + clientset "k8s.io/client-go/kubernetes" + "k8s.io/client-go/rest" + kubeapiservertesting "k8s.io/kubernetes/cmd/kube-apiserver/app/testing" + "k8s.io/kubernetes/test/integration/framework" +) + +const ( + testReinvocationClientUsername = "webhook-reinvocation-integration-client" +) + +// TestWebhookReinvocationPolicy ensures that the admission webhook reinvocation policy is applied correctly. +func TestWebhookReinvocationPolicy(t *testing.T) { + reinvokeNever := registrationv1beta1.NeverReinvocationPolicy + reinvokeIfNeeded := registrationv1beta1.IfNeededReinvocationPolicy + + type testWebhook struct { + path string + policy *registrationv1beta1.ReinvocationPolicyType + objectSelector *metav1.LabelSelector + } + + testCases := []struct { + name string + initialPriorityClass string + webhooks []testWebhook + expectLabels map[string]string + expectInvocations map[string]int + expectError bool + errorContains string + }{ + { // in-tree (mutation), webhook (no mutation), no reinvocation required + name: "no reinvocation for in-tree only mutation", + initialPriorityClass: "low-priority", // trigger initial in-tree mutation + webhooks: []testWebhook{ + {path: "/noop", policy: &reinvokeIfNeeded}, + }, + expectInvocations: map[string]int{"/noop": 1}, + }, + { // in-tree (mutation), webhook (mutation), reinvoke in-tree (no-mutation), no webhook reinvocation required + name: "no webhook reinvocation for webhook when no in-tree reinvocation mutations", + initialPriorityClass: "low-priority", // trigger initial in-tree mutation + webhooks: []testWebhook{ + {path: "/addlabel", policy: &reinvokeIfNeeded}, + }, + expectInvocations: map[string]int{"/addlabel": 1}, + }, + { // in-tree (mutation), webhook (mutation), reinvoke in-tree (mutation), webhook (no-mutation), both reinvoked + name: "webhook is reinvoked after in-tree reinvocation", + initialPriorityClass: "low-priority", // trigger initial in-tree mutation + webhooks: []testWebhook{ + // Priority plugin is ordered to run before mutating webhooks + {path: "/setpriority", policy: &reinvokeIfNeeded}, // trigger in-tree reinvoke mutation + }, + expectInvocations: map[string]int{"/setpriority": 2}, + }, + { // in-tree (mutation), webhook A (mutation), webhook B (mutation), reinvoke in-tree (no-mutation), reinvoke webhook A (no-mutation), no reinvocation of webhook B required + name: "no reinvocation of webhook B when in-tree or prior webhook mutations", + initialPriorityClass: "low-priority", // trigger initial in-tree mutation + webhooks: []testWebhook{ + {path: "/addlabel", policy: &reinvokeIfNeeded}, + {path: "/conditionaladdlabel", policy: &reinvokeIfNeeded}, + }, + expectLabels: map[string]string{"x": "true", "a": "true", "b": "true"}, + expectInvocations: map[string]int{"/addlabel": 2, "/conditionaladdlabel": 1}, + }, + { // in-tree (mutation), webhook A (mutation), webhook B (mutation), reinvoke in-tree (no-mutation), reinvoke webhook A (mutation), reinvoke webhook B (mutation), both webhooks reinvoked + name: "all webhooks reinvoked when any webhook reinvocation causes mutation", + initialPriorityClass: "low-priority", // trigger initial in-tree mutation + webhooks: []testWebhook{ + {path: "/settrue", policy: &reinvokeIfNeeded}, + {path: "/setfalse", policy: &reinvokeIfNeeded}, + }, + expectLabels: map[string]string{"x": "true", "fight": "false"}, + expectInvocations: map[string]int{"/settrue": 2, "/setfalse": 2}, + }, + { // in-tree (mutation), webhook A is SKIPPED due to objectSelector not matching, webhook B (mutation), reinvoke in-tree (no-mutation), webhook A is SKIPPED even though the labels match now, because it's not called in the first round. No reinvocation of webhook B required + name: "no reinvocation of webhook B when in-tree or prior webhook mutations", + initialPriorityClass: "low-priority", // trigger initial in-tree mutation + webhooks: []testWebhook{ + {path: "/conditionaladdlabel", policy: &reinvokeIfNeeded, objectSelector: &metav1.LabelSelector{MatchLabels: map[string]string{"a": "true"}}}, + {path: "/addlabel", policy: &reinvokeIfNeeded}, + }, + expectLabels: map[string]string{"x": "true", "a": "true"}, + expectInvocations: map[string]int{"/addlabel": 1, "/conditionaladdlabel": 0}, + }, + { + name: "invalid priority class set by webhook should result in error from in-tree priority plugin", + webhooks: []testWebhook{ + // Priority plugin is ordered to run before mutating webhooks + {path: "/setinvalidpriority", policy: &reinvokeIfNeeded}, + }, + expectError: true, + errorContains: "no PriorityClass with name invalid was found", + expectInvocations: map[string]int{"/setinvalidpriority": 1}, + }, + { + name: "'reinvoke never' policy respected", + webhooks: []testWebhook{ + {path: "/conditionaladdlabel", policy: &reinvokeNever}, + {path: "/addlabel", policy: &reinvokeNever}, + }, + expectLabels: map[string]string{"x": "true", "a": "true"}, + expectInvocations: map[string]int{"/conditionaladdlabel": 1, "/addlabel": 1}, + }, + { + name: "'reinvoke never' (by default) policy respected", + webhooks: []testWebhook{ + {path: "/conditionaladdlabel", policy: nil}, + {path: "/addlabel", policy: nil}, + }, + expectLabels: map[string]string{"x": "true", "a": "true"}, + expectInvocations: map[string]int{"/conditionaladdlabel": 1, "/addlabel": 1}, + }, + } + + 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 := &invocationRecorder{counts: map[string]int{}} + webhookServer := httptest.NewUnstartedServer(newReinvokeWebhookHandler(recorder)) + webhookServer.TLS = &tls.Config{ + + RootCAs: roots, + Certificates: []tls.Certificate{cert}, + } + webhookServer.StartTLS() + defer webhookServer.Close() + + s := kubeapiservertesting.StartTestServerOrDie(t, kubeapiservertesting.NewDefaultTestServerOptions(), []string{ + "--disable-admission-plugins=ServiceAccount", + }, framework.SharedEtcd()) + defer s.TearDownFn() + + // Configure a client with a distinct user name so that it is easy to distinguish requests + // made by the client from requests made by controllers. We use this to filter out requests + // before recording them to ensure we don't accidentally mistake requests from controllers + // as requests made by the client. + clientConfig := rest.CopyConfig(s.ClientConfig) + clientConfig.Impersonate.UserName = testReinvocationClientUsername + clientConfig.Impersonate.Groups = []string{"system:masters", "system:authenticated"} + client, err := clientset.NewForConfig(clientConfig) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + for priorityClass, priority := range map[string]int{"low-priority": 1, "high-priority": 10} { + _, err = client.SchedulingV1().PriorityClasses().Create(&schedulingv1.PriorityClass{ObjectMeta: metav1.ObjectMeta{Name: priorityClass}, Value: int32(priority)}) + if err != nil { + t.Fatal(err) + } + } + + for i, tt := range testCases { + t.Run(tt.name, func(t *testing.T) { + recorder.Reset() + ns := fmt.Sprintf("reinvoke-%d", i) + _, err = client.CoreV1().Namespaces().Create(&v1.Namespace{ObjectMeta: metav1.ObjectMeta{Name: ns}}) + if err != nil { + t.Fatal(err) + } + + for i, webhook := range tt.webhooks { + defer registerWebhook(t, client, fmt.Sprintf("admission.integration.test%d", i), webhookServer.URL+webhook.path, webhook.policy, webhook.objectSelector)() + } + + pod := &corev1.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Namespace: ns, + Name: "labeled", + Labels: map[string]string{"x": "true"}, + }, + Spec: corev1.PodSpec{ + Containers: []v1.Container{{ + Name: "fake-name", + Image: "fakeimage", + }}, + }, + } + if tt.initialPriorityClass != "" { + pod.Spec.PriorityClassName = tt.initialPriorityClass + } + obj, err := client.CoreV1().Pods(ns).Create(pod) + + if tt.expectError { + if err == nil { + t.Fatalf("expected error but got none") + } + if tt.errorContains != "" { + if !strings.Contains(err.Error(), tt.errorContains) { + t.Errorf("expected an error saying %q, but got: %v", tt.errorContains, err) + } + } + return + } + + if err != nil { + t.Fatal(err) + } + + if tt.expectLabels != nil { + labels := obj.GetLabels() + if !reflect.DeepEqual(tt.expectLabels, labels) { + t.Errorf("expected labels '%v', but got '%v'", tt.expectLabels, labels) + } + } + + if tt.expectInvocations != nil { + for k, v := range tt.expectInvocations { + if recorder.GetCount(k) != v { + t.Errorf("expected %d invocations of %s, but got %d", v, k, recorder.GetCount(k)) + } + } + } + }) + } +} + +func registerWebhook(t *testing.T, client clientset.Interface, name, endpoint string, reinvocationPolicy *registrationv1beta1.ReinvocationPolicyType, objectSelector *metav1.LabelSelector) func() { + fail := admissionv1beta1.Fail + hook, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{ + ObjectMeta: metav1.ObjectMeta{Name: name}, + Webhooks: []admissionv1beta1.MutatingWebhook{{ + Name: name, + ClientConfig: admissionv1beta1.WebhookClientConfig{ + URL: &endpoint, + CABundle: localhostCert, + }, + Rules: []admissionv1beta1.RuleWithOperations{{ + Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll}, + Rule: admissionv1beta1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}}, + }}, + ObjectSelector: objectSelector, + FailurePolicy: &fail, + ReinvocationPolicy: reinvocationPolicy, + AdmissionReviewVersions: []string{"v1beta1"}, + }}, + }) + if err != nil { + t.Fatal(err) + } + + tearDown := func() { + err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Delete(hook.GetName(), &metav1.DeleteOptions{}) + if err != nil { + t.Fatal(err) + } + } + return tearDown +} + +type invocationRecorder struct { + mu sync.Mutex + counts map[string]int +} + +func (i *invocationRecorder) Reset() { + i.mu.Lock() + defer i.mu.Unlock() + i.counts = map[string]int{} +} + +func (i *invocationRecorder) GetCount(path string) int { + i.mu.Lock() + defer i.mu.Unlock() + return i.counts[path] +} + +func (i *invocationRecorder) IncrementCount(path string) { + i.mu.Lock() + defer i.mu.Unlock() + i.counts[path]++ +} + +func newReinvokeWebhookHandler(recorder *invocationRecorder) http.Handler { + patch := func(w http.ResponseWriter, patch string) { + w.Header().Set("Content-Type", "application/json") + pt := v1beta1.PatchTypeJSONPatch + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: true, + PatchType: &pt, + Patch: []byte(patch), + }, + }) + } + allow := func(w http.ResponseWriter) { + w.Header().Set("Content-Type", "application/json") + json.NewEncoder(w).Encode(&v1beta1.AdmissionReview{ + Response: &v1beta1.AdmissionResponse{ + Allowed: true, + }, + }) + } + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + defer r.Body.Close() + data, err := ioutil.ReadAll(r.Body) + if err != nil { + http.Error(w, err.Error(), 400) + } + review := v1beta1.AdmissionReview{} + if err := json.Unmarshal(data, &review); err != nil { + http.Error(w, err.Error(), 400) + } + if review.Request.UserInfo.Username != testReinvocationClientUsername { + // skip requests not originating from this integration test's client + allow(w) + return + } + + if len(review.Request.Object.Raw) == 0 { + http.Error(w, err.Error(), 400) + } + pod := &corev1.Pod{} + if err := json.Unmarshal(review.Request.Object.Raw, pod); err != nil { + http.Error(w, err.Error(), 400) + } + + recorder.IncrementCount(r.URL.Path) + + switch r.URL.Path { + case "/noop": + allow(w) + case "/settrue": + patch(w, `[{"op": "replace", "path": "/metadata/labels/fight", "value": "true"}]`) + case "/setfalse": + patch(w, `[{"op": "replace", "path": "/metadata/labels/fight", "value": "false"}]`) + case "/addlabel": + labels := pod.GetLabels() + if a, ok := labels["a"]; !ok || a != "true" { + patch(w, `[{"op": "add", "path": "/metadata/labels/a", "value": "true"}]`) + return + } + allow(w) + case "/conditionaladdlabel": // if 'a' is set, set 'b' to true + labels := pod.GetLabels() + if _, ok := labels["a"]; ok { + patch(w, `[{"op": "add", "path": "/metadata/labels/b", "value": "true"}]`) + return + } + allow(w) + case "/setpriority": // sets /spec/priorityClassName to high-priority if it is not already set + if pod.Spec.PriorityClassName != "high-priority" { + if pod.Spec.Priority != nil { + patch(w, `[{"op": "add", "path": "/spec/priorityClassName", "value": "high-priority"},{"op": "remove", "path": "/spec/priority"}]`) + } else { + patch(w, `[{"op": "add", "path": "/spec/priorityClassName", "value": "high-priority"}]`) + } + return + } + allow(w) + case "/setinvalidpriority": + patch(w, `[{"op": "add", "path": "/spec/priorityClassName", "value": "invalid"}]`) + default: + http.NotFound(w, r) + } + }) +} diff --git a/test/integration/examples/webhook_test.go b/test/integration/examples/webhook_test.go index 611925a5f96..d5ce5db6bc7 100644 --- a/test/integration/examples/webhook_test.go +++ b/test/integration/examples/webhook_test.go @@ -22,7 +22,7 @@ import ( "time" admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/util/wait" auditinternal "k8s.io/apiserver/pkg/apis/audit" @@ -65,7 +65,7 @@ func TestWebhookLoopback(t *testing.T) { fail := admissionv1beta1.Fail _, err := client.AdmissionregistrationV1beta1().MutatingWebhookConfigurations().Create(&admissionv1beta1.MutatingWebhookConfiguration{ ObjectMeta: metav1.ObjectMeta{Name: "webhooktest.example.com"}, - Webhooks: []admissionv1beta1.Webhook{{ + Webhooks: []admissionv1beta1.MutatingWebhook{{ Name: "webhooktest.example.com", ClientConfig: admissionv1beta1.WebhookClientConfig{ Service: &admissionv1beta1.ServiceReference{Namespace: "default", Name: "kubernetes", Path: &webhookPath}, diff --git a/vendor/modules.txt b/vendor/modules.txt index 39ea9492cdc..d9df4ec3b85 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -1185,6 +1185,7 @@ k8s.io/apiserver/pkg/admission/configuration k8s.io/apiserver/pkg/admission/initializer k8s.io/apiserver/pkg/admission/metrics k8s.io/apiserver/pkg/admission/plugin/namespace/lifecycle +k8s.io/apiserver/pkg/admission/plugin/webhook k8s.io/apiserver/pkg/admission/plugin/webhook/config k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission k8s.io/apiserver/pkg/admission/plugin/webhook/config/apis/webhookadmission/v1alpha1 @@ -1193,6 +1194,7 @@ k8s.io/apiserver/pkg/admission/plugin/webhook/generic k8s.io/apiserver/pkg/admission/plugin/webhook/initializer k8s.io/apiserver/pkg/admission/plugin/webhook/mutating k8s.io/apiserver/pkg/admission/plugin/webhook/namespace +k8s.io/apiserver/pkg/admission/plugin/webhook/object k8s.io/apiserver/pkg/admission/plugin/webhook/request k8s.io/apiserver/pkg/admission/plugin/webhook/rules k8s.io/apiserver/pkg/admission/plugin/webhook/util