Merge pull request #80231 from liggitt/admissionreview-v1

Promote admissionreview to v1
This commit is contained in:
Kubernetes Prow Robot 2019-08-01 17:20:05 -07:00 committed by GitHub
commit c981c65c90
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
52 changed files with 4121 additions and 97 deletions

View File

@ -17968,6 +17968,11 @@
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "admission.k8s.io",
"kind": "DeleteOptions",
"version": "v1"
},
{
"group": "admission.k8s.io",
"kind": "DeleteOptions",
@ -18585,6 +18590,11 @@
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "admission.k8s.io",
"kind": "WatchEvent",
"version": "v1"
},
{
"group": "admission.k8s.io",
"kind": "WatchEvent",

View File

@ -27,6 +27,7 @@ kazel_configured_tags = ["openapi-gen"]
# tags_values_pkgs is a dictionary mapping {k8s build tag: {tag value: [pkgs including that tag:value]}}
tags_values_pkgs = {"openapi-gen": {
"false": [
"staging/src/k8s.io/api/admission/v1",
"staging/src/k8s.io/api/admission/v1beta1",
"staging/src/k8s.io/api/core/v1",
"staging/src/k8s.io/apimachinery/pkg/apis/testapigroup/v1",
@ -114,6 +115,7 @@ tags_pkgs_values = {"openapi-gen": {
"pkg/apis/abac/v1beta1": ["true"],
"pkg/apis/auditregistration": ["true"],
"pkg/version": ["true"],
"staging/src/k8s.io/api/admission/v1": ["false"],
"staging/src/k8s.io/api/admission/v1beta1": ["false"],
"staging/src/k8s.io/api/admissionregistration/v1": ["true"],
"staging/src/k8s.io/api/admissionregistration/v1beta1": ["true"],

View File

@ -302,6 +302,7 @@ plugin/pkg/admission/resourcequota/apis/resourcequota/v1beta1
plugin/pkg/auth/authorizer/node
plugin/pkg/auth/authorizer/rbac
plugin/pkg/auth/authorizer/rbac/bootstrappolicy
staging/src/k8s.io/api/admission/v1
staging/src/k8s.io/api/admission/v1beta1
staging/src/k8s.io/api/admissionregistration/v1
staging/src/k8s.io/api/admissionregistration/v1beta1

View File

@ -2,6 +2,7 @@
"k8s.io/api/admissionregistration/v1": "admissionregistrationv1",
"k8s.io/api/admissionregistration/v1beta1": "admissionregistrationv1beta1",
"k8s.io/api/admission/v1beta1": "admissionv1beta1",
"k8s.io/api/admission/v1": "admissionv1",
"k8s.io/api/apps/v1": "appsv1",
"k8s.io/api/apps/v1beta1": "appsv1beta1",
"k8s.io/api/apps/v1beta2": "appsv1beta2",

View File

@ -65,6 +65,7 @@ KUBE_AVAILABLE_GROUP_VERSIONS="${KUBE_AVAILABLE_GROUP_VERSIONS:-\
v1 \
admissionregistration.k8s.io/v1 \
admissionregistration.k8s.io/v1beta1 \
admission.k8s.io/v1 \
admission.k8s.io/v1beta1 \
apps/v1 \
apps/v1beta1 \
@ -110,6 +111,7 @@ KUBE_NONSERVER_GROUP_VERSIONS="
abac.authorization.kubernetes.io/v1beta1 \
componentconfig/v1alpha1 \
imagepolicy.k8s.io/v1alpha1\
admission.k8s.io/v1\
admission.k8s.io/v1beta1\
"
export KUBE_NONSERVER_GROUP_VERSIONS

View File

@ -36,6 +36,7 @@ filegroup(
":package-srcs",
"//pkg/apis/admission/fuzzer:all-srcs",
"//pkg/apis/admission/install:all-srcs",
"//pkg/apis/admission/v1:all-srcs",
"//pkg/apis/admission/v1beta1:all-srcs",
],
tags = ["automanaged"],

View File

@ -12,6 +12,7 @@ go_library(
deps = [
"//pkg/api/legacyscheme:go_default_library",
"//pkg/apis/admission:go_default_library",
"//pkg/apis/admission/v1:go_default_library",
"//pkg/apis/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",

View File

@ -23,6 +23,7 @@ import (
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
"k8s.io/kubernetes/pkg/api/legacyscheme"
"k8s.io/kubernetes/pkg/apis/admission"
v1 "k8s.io/kubernetes/pkg/apis/admission/v1"
"k8s.io/kubernetes/pkg/apis/admission/v1beta1"
)
@ -34,5 +35,6 @@ func init() {
func Install(scheme *runtime.Scheme) {
utilruntime.Must(admission.AddToScheme(scheme))
utilruntime.Must(v1beta1.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1beta1.SchemeGroupVersion))
utilruntime.Must(v1.AddToScheme(scheme))
utilruntime.Must(scheme.SetVersionPriority(v1.SchemeGroupVersion, v1beta1.SchemeGroupVersion))
}

View File

@ -95,10 +95,10 @@ type AdmissionRequest struct {
Operation Operation
// UserInfo is information about the requesting user
UserInfo authentication.UserInfo
// Object is the object from the incoming request prior to default values being applied
// Object is the object from the incoming request.
// +optional
Object runtime.Object
// OldObject is the existing object. Only populated for UPDATE requests.
// OldObject is the existing object. Only populated for DELETE and UPDATE requests.
// +optional
OldObject runtime.Object
// DryRun indicates that modifications will definitely not be persisted for this request.

View File

@ -0,0 +1,39 @@
package(default_visibility = ["//visibility:public"])
load(
"@io_bazel_rules_go//go:def.bzl",
"go_library",
)
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"register.go",
"zz_generated.conversion.go",
"zz_generated.defaults.go",
],
importpath = "k8s.io/kubernetes/pkg/apis/admission/v1",
deps = [
"//pkg/apis/admission:go_default_library",
"//staging/src/k8s.io/api/admission/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/conversion: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/types:go_default_library",
],
)
filegroup(
name = "package-srcs",
srcs = glob(["**"]),
tags = ["automanaged"],
visibility = ["//visibility:private"],
)
filegroup(
name = "all-srcs",
srcs = [":package-srcs"],
tags = ["automanaged"],
)

View File

@ -0,0 +1,24 @@
/*
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.
*/
// +k8s:conversion-gen=k8s.io/kubernetes/pkg/apis/admission
// +k8s:conversion-gen-external-types=k8s.io/api/admission/v1
// +k8s:defaulter-gen=TypeMeta
// +k8s:defaulter-gen-input=../../../../vendor/k8s.io/api/admission/v1
// +groupName=admission.k8s.io
package v1 // import "k8s.io/kubernetes/pkg/apis/admission/v1"

View File

@ -0,0 +1,46 @@
/*
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 v1
import (
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name for this API.
const GroupName = "admission.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
localSchemeBuilder = &admissionv1.SchemeBuilder
// AddToScheme is a common registration function for mapping packaged scoped group & version keys to a scheme
AddToScheme = localSchemeBuilder.AddToScheme
)
func init() {
// We only register manually written functions here. The registration of the
// generated functions takes place in the generated files. The separation
// makes the code compile even when the generated files are missing.
localSchemeBuilder.Register(RegisterDefaults)
}

View File

@ -0,0 +1,206 @@
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by conversion-gen. DO NOT EDIT.
package v1
import (
unsafe "unsafe"
v1 "k8s.io/api/admission/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
conversion "k8s.io/apimachinery/pkg/conversion"
runtime "k8s.io/apimachinery/pkg/runtime"
types "k8s.io/apimachinery/pkg/types"
admission "k8s.io/kubernetes/pkg/apis/admission"
)
func init() {
localSchemeBuilder.Register(RegisterConversions)
}
// RegisterConversions adds conversion functions to the given scheme.
// Public to allow building arbitrary schemes.
func RegisterConversions(s *runtime.Scheme) error {
if err := s.AddGeneratedConversionFunc((*v1.AdmissionRequest)(nil), (*admission.AdmissionRequest)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_AdmissionRequest_To_admission_AdmissionRequest(a.(*v1.AdmissionRequest), b.(*admission.AdmissionRequest), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*admission.AdmissionRequest)(nil), (*v1.AdmissionRequest)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_admission_AdmissionRequest_To_v1_AdmissionRequest(a.(*admission.AdmissionRequest), b.(*v1.AdmissionRequest), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.AdmissionResponse)(nil), (*admission.AdmissionResponse)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_AdmissionResponse_To_admission_AdmissionResponse(a.(*v1.AdmissionResponse), b.(*admission.AdmissionResponse), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*admission.AdmissionResponse)(nil), (*v1.AdmissionResponse)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_admission_AdmissionResponse_To_v1_AdmissionResponse(a.(*admission.AdmissionResponse), b.(*v1.AdmissionResponse), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*v1.AdmissionReview)(nil), (*admission.AdmissionReview)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_v1_AdmissionReview_To_admission_AdmissionReview(a.(*v1.AdmissionReview), b.(*admission.AdmissionReview), scope)
}); err != nil {
return err
}
if err := s.AddGeneratedConversionFunc((*admission.AdmissionReview)(nil), (*v1.AdmissionReview)(nil), func(a, b interface{}, scope conversion.Scope) error {
return Convert_admission_AdmissionReview_To_v1_AdmissionReview(a.(*admission.AdmissionReview), b.(*v1.AdmissionReview), scope)
}); err != nil {
return err
}
return nil
}
func autoConvert_v1_AdmissionRequest_To_admission_AdmissionRequest(in *v1.AdmissionRequest, out *admission.AdmissionRequest, s conversion.Scope) error {
out.UID = types.UID(in.UID)
out.Kind = in.Kind
out.Resource = in.Resource
out.SubResource = in.SubResource
out.RequestKind = (*metav1.GroupVersionKind)(unsafe.Pointer(in.RequestKind))
out.RequestResource = (*metav1.GroupVersionResource)(unsafe.Pointer(in.RequestResource))
out.RequestSubResource = in.RequestSubResource
out.Name = in.Name
out.Namespace = in.Namespace
out.Operation = admission.Operation(in.Operation)
// TODO: Inefficient conversion - can we improve it?
if err := s.Convert(&in.UserInfo, &out.UserInfo, 0); err != nil {
return err
}
if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Object, &out.Object, s); err != nil {
return err
}
if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.OldObject, &out.OldObject, s); err != nil {
return err
}
out.DryRun = (*bool)(unsafe.Pointer(in.DryRun))
if err := runtime.Convert_runtime_RawExtension_To_runtime_Object(&in.Options, &out.Options, s); err != nil {
return err
}
return nil
}
// Convert_v1_AdmissionRequest_To_admission_AdmissionRequest is an autogenerated conversion function.
func Convert_v1_AdmissionRequest_To_admission_AdmissionRequest(in *v1.AdmissionRequest, out *admission.AdmissionRequest, s conversion.Scope) error {
return autoConvert_v1_AdmissionRequest_To_admission_AdmissionRequest(in, out, s)
}
func autoConvert_admission_AdmissionRequest_To_v1_AdmissionRequest(in *admission.AdmissionRequest, out *v1.AdmissionRequest, s conversion.Scope) error {
out.UID = types.UID(in.UID)
out.Kind = in.Kind
out.Resource = in.Resource
out.SubResource = in.SubResource
out.RequestKind = (*metav1.GroupVersionKind)(unsafe.Pointer(in.RequestKind))
out.RequestResource = (*metav1.GroupVersionResource)(unsafe.Pointer(in.RequestResource))
out.RequestSubResource = in.RequestSubResource
out.Name = in.Name
out.Namespace = in.Namespace
out.Operation = v1.Operation(in.Operation)
// TODO: Inefficient conversion - can we improve it?
if err := s.Convert(&in.UserInfo, &out.UserInfo, 0); err != nil {
return err
}
if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Object, &out.Object, s); err != nil {
return err
}
if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.OldObject, &out.OldObject, s); err != nil {
return err
}
out.DryRun = (*bool)(unsafe.Pointer(in.DryRun))
if err := runtime.Convert_runtime_Object_To_runtime_RawExtension(&in.Options, &out.Options, s); err != nil {
return err
}
return nil
}
// Convert_admission_AdmissionRequest_To_v1_AdmissionRequest is an autogenerated conversion function.
func Convert_admission_AdmissionRequest_To_v1_AdmissionRequest(in *admission.AdmissionRequest, out *v1.AdmissionRequest, s conversion.Scope) error {
return autoConvert_admission_AdmissionRequest_To_v1_AdmissionRequest(in, out, s)
}
func autoConvert_v1_AdmissionResponse_To_admission_AdmissionResponse(in *v1.AdmissionResponse, out *admission.AdmissionResponse, s conversion.Scope) error {
out.UID = types.UID(in.UID)
out.Allowed = in.Allowed
out.Result = (*metav1.Status)(unsafe.Pointer(in.Result))
out.Patch = *(*[]byte)(unsafe.Pointer(&in.Patch))
out.PatchType = (*admission.PatchType)(unsafe.Pointer(in.PatchType))
out.AuditAnnotations = *(*map[string]string)(unsafe.Pointer(&in.AuditAnnotations))
return nil
}
// Convert_v1_AdmissionResponse_To_admission_AdmissionResponse is an autogenerated conversion function.
func Convert_v1_AdmissionResponse_To_admission_AdmissionResponse(in *v1.AdmissionResponse, out *admission.AdmissionResponse, s conversion.Scope) error {
return autoConvert_v1_AdmissionResponse_To_admission_AdmissionResponse(in, out, s)
}
func autoConvert_admission_AdmissionResponse_To_v1_AdmissionResponse(in *admission.AdmissionResponse, out *v1.AdmissionResponse, s conversion.Scope) error {
out.UID = types.UID(in.UID)
out.Allowed = in.Allowed
out.Result = (*metav1.Status)(unsafe.Pointer(in.Result))
out.Patch = *(*[]byte)(unsafe.Pointer(&in.Patch))
out.PatchType = (*v1.PatchType)(unsafe.Pointer(in.PatchType))
out.AuditAnnotations = *(*map[string]string)(unsafe.Pointer(&in.AuditAnnotations))
return nil
}
// Convert_admission_AdmissionResponse_To_v1_AdmissionResponse is an autogenerated conversion function.
func Convert_admission_AdmissionResponse_To_v1_AdmissionResponse(in *admission.AdmissionResponse, out *v1.AdmissionResponse, s conversion.Scope) error {
return autoConvert_admission_AdmissionResponse_To_v1_AdmissionResponse(in, out, s)
}
func autoConvert_v1_AdmissionReview_To_admission_AdmissionReview(in *v1.AdmissionReview, out *admission.AdmissionReview, s conversion.Scope) error {
if in.Request != nil {
in, out := &in.Request, &out.Request
*out = new(admission.AdmissionRequest)
if err := Convert_v1_AdmissionRequest_To_admission_AdmissionRequest(*in, *out, s); err != nil {
return err
}
} else {
out.Request = nil
}
out.Response = (*admission.AdmissionResponse)(unsafe.Pointer(in.Response))
return nil
}
// Convert_v1_AdmissionReview_To_admission_AdmissionReview is an autogenerated conversion function.
func Convert_v1_AdmissionReview_To_admission_AdmissionReview(in *v1.AdmissionReview, out *admission.AdmissionReview, s conversion.Scope) error {
return autoConvert_v1_AdmissionReview_To_admission_AdmissionReview(in, out, s)
}
func autoConvert_admission_AdmissionReview_To_v1_AdmissionReview(in *admission.AdmissionReview, out *v1.AdmissionReview, s conversion.Scope) error {
if in.Request != nil {
in, out := &in.Request, &out.Request
*out = new(v1.AdmissionRequest)
if err := Convert_admission_AdmissionRequest_To_v1_AdmissionRequest(*in, *out, s); err != nil {
return err
}
} else {
out.Request = nil
}
out.Response = (*v1.AdmissionResponse)(unsafe.Pointer(in.Response))
return nil
}
// Convert_admission_AdmissionReview_To_v1_AdmissionReview is an autogenerated conversion function.
func Convert_admission_AdmissionReview_To_v1_AdmissionReview(in *admission.AdmissionReview, out *v1.AdmissionReview, s conversion.Scope) error {
return autoConvert_admission_AdmissionReview_To_v1_AdmissionReview(in, out, s)
}

View File

@ -0,0 +1,32 @@
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by defaulter-gen. DO NOT EDIT.
package v1
import (
runtime "k8s.io/apimachinery/pkg/runtime"
)
// RegisterDefaults adds defaulters functions to the given scheme.
// Public to allow building arbitrary schemes.
// All generated defaulters are covering - they call all nested defaulters.
func RegisterDefaults(scheme *runtime.Scheme) error {
return nil
}

View File

@ -152,6 +152,10 @@ func validateRule(rule *admissionregistration.Rule, fldPath *field.Path, allowSu
return allErrors
}
// AcceptedAdmissionReviewVersions contains the list of AdmissionReview versions the *prior* version of the API server understands.
// 1.15: server understands v1beta1; accepted versions are ["v1beta1"]
// 1.16: server understands v1, v1beta1; accepted versions are ["v1beta1"]
// 1.17: server understands v1, v1beta1; accepted versions are ["v1","v1beta1"]
var AcceptedAdmissionReviewVersions = []string{v1beta1.SchemeGroupVersion.Version}
func isAcceptedAdmissionReviewVersion(v string) bool {
@ -188,7 +192,7 @@ func validateAdmissionReviewVersions(versions []string, requireRecognizedVersion
if requireRecognizedVersion && !hasAcceptedVersion {
allErrors = append(allErrors, field.Invalid(
fldPath, versions,
fmt.Sprintf("none of the versions accepted by this server. accepted version(s) are %v",
fmt.Sprintf("must include at least one of %v",
strings.Join(AcceptedAdmissionReviewVersions, ", "))))
}
}

View File

@ -82,6 +82,7 @@
"k8s.io/api/authorization/v1",
"k8s.io/api/settings/v1alpha1",
"k8s.io/api/admission/v1beta1",
"k8s.io/api/admission/v1",
"k8s.io/api/networking/v1",
"k8s.io/component-base/config",
"k8s.io/component-base/config/v1alpha1",

View File

@ -5,6 +5,7 @@ go_test(
srcs = ["roundtrip_test.go"],
data = glob(["testdata/**"]),
deps = [
"//staging/src/k8s.io/api/admission/v1:go_default_library",
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
@ -63,6 +64,7 @@ filegroup(
name = "all-srcs",
srcs = [
":package-srcs",
"//staging/src/k8s.io/api/admission/v1:all-srcs",
"//staging/src/k8s.io/api/admission/v1beta1:all-srcs",
"//staging/src/k8s.io/api/admissionregistration/v1:all-srcs",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:all-srcs",

View File

@ -0,0 +1,39 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
go_library(
name = "go_default_library",
srcs = [
"doc.go",
"generated.pb.go",
"register.go",
"types.go",
"types_swagger_doc_generated.go",
"zz_generated.deepcopy.go",
],
importmap = "k8s.io/kubernetes/vendor/k8s.io/api/admission/v1",
importpath = "k8s.io/api/admission/v1",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/authentication/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1: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/types:go_default_library",
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
"//vendor/github.com/gogo/protobuf/sortkeys: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"],
)

View File

@ -0,0 +1,23 @@
/*
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.
*/
// +k8s:deepcopy-gen=package
// +k8s:protobuf-gen=package
// +k8s:openapi-gen=false
// +groupName=admission.k8s.io
package v1 // import "k8s.io/api/admission/v1"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,160 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// This file was autogenerated by go-to-protobuf. Do not edit it manually!
syntax = 'proto2';
package k8s.io.api.admission.v1;
import "k8s.io/api/authentication/v1/generated.proto";
import "k8s.io/apimachinery/pkg/apis/meta/v1/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/generated.proto";
import "k8s.io/apimachinery/pkg/runtime/schema/generated.proto";
// Package-wide variables from generator "generated".
option go_package = "v1";
// AdmissionRequest describes the admission.Attributes for the admission request.
message AdmissionRequest {
// UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are
// otherwise identical (parallel requests, requests when earlier requests did not modify etc)
// The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request.
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
optional string uid = 1;
// Kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale)
optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind kind = 2;
// Resource is the fully-qualified resource being requested (for example, v1.pods)
optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource resource = 3;
// SubResource is the subresource being requested, if any (for example, "status" or "scale")
// +optional
optional string subResource = 4;
// RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale).
// If this is specified and differs from the value in "kind", an equivalent match and conversion was performed.
//
// For example, if deployments can be modified via apps/v1 and apps/v1, and a webhook registered a rule of
// `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`,
// an API request to apps/v1 deployments would be converted and sent to the webhook
// with `kind: {group:"apps", version:"v1", kind:"Deployment"}` (matching the rule the webhook registered for),
// and `requestKind: {group:"apps", version:"v1", kind:"Deployment"}` (indicating the kind of the original API request).
//
// See documentation for the "matchPolicy" field in the webhook configuration type for more details.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionKind requestKind = 13;
// RequestResource is the fully-qualified resource of the original API request (for example, v1.pods).
// If this is specified and differs from the value in "resource", an equivalent match and conversion was performed.
//
// For example, if deployments can be modified via apps/v1 and apps/v1, and a webhook registered a rule of
// `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`,
// an API request to apps/v1 deployments would be converted and sent to the webhook
// with `resource: {group:"apps", version:"v1", resource:"deployments"}` (matching the resource the webhook registered for),
// and `requestResource: {group:"apps", version:"v1", resource:"deployments"}` (indicating the resource of the original API request).
//
// See documentation for the "matchPolicy" field in the webhook configuration type.
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.GroupVersionResource requestResource = 14;
// RequestSubResource is the name of the subresource of the original API request, if any (for example, "status" or "scale")
// If this is specified and differs from the value in "subResource", an equivalent match and conversion was performed.
// See documentation for the "matchPolicy" field in the webhook configuration type.
// +optional
optional string requestSubResource = 15;
// Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and
// rely on the server to generate the name. If that is the case, this field will contain an empty string.
// +optional
optional string name = 5;
// Namespace is the namespace associated with the request (if any).
// +optional
optional string namespace = 6;
// Operation is the operation being performed. This may be different than the operation
// requested. e.g. a patch can result in either a CREATE or UPDATE Operation.
optional string operation = 7;
// UserInfo is information about the requesting user
optional k8s.io.api.authentication.v1.UserInfo userInfo = 8;
// Object is the object from the incoming request.
// +optional
optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 9;
// OldObject is the existing object. Only populated for DELETE and UPDATE requests.
// +optional
optional k8s.io.apimachinery.pkg.runtime.RawExtension oldObject = 10;
// DryRun indicates that modifications will definitely not be persisted for this request.
// Defaults to false.
// +optional
optional bool dryRun = 11;
// Options is the operation option structure of the operation being performed.
// e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be
// different than the options the caller provided. e.g. for a patch request the performed
// Operation might be a CREATE, in which case the Options will a
// `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`.
// +optional
optional k8s.io.apimachinery.pkg.runtime.RawExtension options = 12;
}
// AdmissionResponse describes an admission response.
message AdmissionResponse {
// UID is an identifier for the individual request/response.
// This must be copied over from the corresponding AdmissionRequest.
optional string uid = 1;
// Allowed indicates whether or not the admission request was permitted.
optional bool allowed = 2;
// Result contains extra details into why an admission request was denied.
// This field IS NOT consulted in any way if "Allowed" is "true".
// +optional
optional k8s.io.apimachinery.pkg.apis.meta.v1.Status status = 3;
// The patch body. Currently we only support "JSONPatch" which implements RFC 6902.
// +optional
optional bytes patch = 4;
// The type of Patch. Currently we only allow "JSONPatch".
// +optional
optional string patchType = 5;
// AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted).
// MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controller will prefix the keys with
// admission webhook name (e.g. imagepolicy.example.com/error=image-blacklisted). AuditAnnotations will be provided by
// the admission webhook to add additional context to the audit log for this request.
// +optional
map<string, string> auditAnnotations = 6;
}
// AdmissionReview describes an admission review request/response.
message AdmissionReview {
// Request describes the attributes for the admission request.
// +optional
optional AdmissionRequest request = 1;
// Response describes the attributes for the admission response.
// +optional
optional AdmissionResponse response = 2;
}

View File

@ -0,0 +1,51 @@
/*
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 v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
)
// GroupName is the group name for this API.
const GroupName = "admission.k8s.io"
// SchemeGroupVersion is group version used to register these objects
var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1"}
// Resource takes an unqualified resource and returns a Group qualified GroupResource
func Resource(resource string) schema.GroupResource {
return SchemeGroupVersion.WithResource(resource).GroupResource()
}
var (
// TODO: move SchemeBuilder with zz_generated.deepcopy.go to k8s.io/api.
// localSchemeBuilder and AddToScheme will stay in k8s.io/kubernetes.
SchemeBuilder = runtime.NewSchemeBuilder(addKnownTypes)
localSchemeBuilder = &SchemeBuilder
AddToScheme = localSchemeBuilder.AddToScheme
)
// Adds the list of known types to the given scheme.
func addKnownTypes(scheme *runtime.Scheme) error {
scheme.AddKnownTypes(SchemeGroupVersion,
&AdmissionReview{},
)
metav1.AddToGroupVersion(scheme, SchemeGroupVersion)
return nil
}

View File

@ -0,0 +1,162 @@
/*
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 v1
import (
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
)
// +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
// AdmissionReview describes an admission review request/response.
type AdmissionReview struct {
metav1.TypeMeta `json:",inline"`
// Request describes the attributes for the admission request.
// +optional
Request *AdmissionRequest `json:"request,omitempty" protobuf:"bytes,1,opt,name=request"`
// Response describes the attributes for the admission response.
// +optional
Response *AdmissionResponse `json:"response,omitempty" protobuf:"bytes,2,opt,name=response"`
}
// AdmissionRequest describes the admission.Attributes for the admission request.
type AdmissionRequest struct {
// UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are
// otherwise identical (parallel requests, requests when earlier requests did not modify etc)
// The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request.
// It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.
UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"`
// Kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale)
Kind metav1.GroupVersionKind `json:"kind" protobuf:"bytes,2,opt,name=kind"`
// Resource is the fully-qualified resource being requested (for example, v1.pods)
Resource metav1.GroupVersionResource `json:"resource" protobuf:"bytes,3,opt,name=resource"`
// SubResource is the subresource being requested, if any (for example, "status" or "scale")
// +optional
SubResource string `json:"subResource,omitempty" protobuf:"bytes,4,opt,name=subResource"`
// RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale).
// If this is specified and differs from the value in "kind", an equivalent match and conversion was performed.
//
// For example, if deployments can be modified via apps/v1 and apps/v1, and a webhook registered a rule of
// `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`,
// an API request to apps/v1 deployments would be converted and sent to the webhook
// with `kind: {group:"apps", version:"v1", kind:"Deployment"}` (matching the rule the webhook registered for),
// and `requestKind: {group:"apps", version:"v1", kind:"Deployment"}` (indicating the kind of the original API request).
//
// See documentation for the "matchPolicy" field in the webhook configuration type for more details.
// +optional
RequestKind *metav1.GroupVersionKind `json:"requestKind,omitempty" protobuf:"bytes,13,opt,name=requestKind"`
// RequestResource is the fully-qualified resource of the original API request (for example, v1.pods).
// If this is specified and differs from the value in "resource", an equivalent match and conversion was performed.
//
// For example, if deployments can be modified via apps/v1 and apps/v1, and a webhook registered a rule of
// `apiGroups:["apps"], apiVersions:["v1"], resources: ["deployments"]` and `matchPolicy: Equivalent`,
// an API request to apps/v1 deployments would be converted and sent to the webhook
// with `resource: {group:"apps", version:"v1", resource:"deployments"}` (matching the resource the webhook registered for),
// and `requestResource: {group:"apps", version:"v1", resource:"deployments"}` (indicating the resource of the original API request).
//
// See documentation for the "matchPolicy" field in the webhook configuration type.
// +optional
RequestResource *metav1.GroupVersionResource `json:"requestResource,omitempty" protobuf:"bytes,14,opt,name=requestResource"`
// RequestSubResource is the name of the subresource of the original API request, if any (for example, "status" or "scale")
// If this is specified and differs from the value in "subResource", an equivalent match and conversion was performed.
// See documentation for the "matchPolicy" field in the webhook configuration type.
// +optional
RequestSubResource string `json:"requestSubResource,omitempty" protobuf:"bytes,15,opt,name=requestSubResource"`
// Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and
// rely on the server to generate the name. If that is the case, this field will contain an empty string.
// +optional
Name string `json:"name,omitempty" protobuf:"bytes,5,opt,name=name"`
// Namespace is the namespace associated with the request (if any).
// +optional
Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"`
// Operation is the operation being performed. This may be different than the operation
// requested. e.g. a patch can result in either a CREATE or UPDATE Operation.
Operation Operation `json:"operation" protobuf:"bytes,7,opt,name=operation"`
// UserInfo is information about the requesting user
UserInfo authenticationv1.UserInfo `json:"userInfo" protobuf:"bytes,8,opt,name=userInfo"`
// Object is the object from the incoming request.
// +optional
Object runtime.RawExtension `json:"object,omitempty" protobuf:"bytes,9,opt,name=object"`
// OldObject is the existing object. Only populated for DELETE and UPDATE requests.
// +optional
OldObject runtime.RawExtension `json:"oldObject,omitempty" protobuf:"bytes,10,opt,name=oldObject"`
// DryRun indicates that modifications will definitely not be persisted for this request.
// Defaults to false.
// +optional
DryRun *bool `json:"dryRun,omitempty" protobuf:"varint,11,opt,name=dryRun"`
// Options is the operation option structure of the operation being performed.
// e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be
// different than the options the caller provided. e.g. for a patch request the performed
// Operation might be a CREATE, in which case the Options will a
// `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`.
// +optional
Options runtime.RawExtension `json:"options,omitempty" protobuf:"bytes,12,opt,name=options"`
}
// AdmissionResponse describes an admission response.
type AdmissionResponse struct {
// UID is an identifier for the individual request/response.
// This must be copied over from the corresponding AdmissionRequest.
UID types.UID `json:"uid" protobuf:"bytes,1,opt,name=uid"`
// Allowed indicates whether or not the admission request was permitted.
Allowed bool `json:"allowed" protobuf:"varint,2,opt,name=allowed"`
// Result contains extra details into why an admission request was denied.
// This field IS NOT consulted in any way if "Allowed" is "true".
// +optional
Result *metav1.Status `json:"status,omitempty" protobuf:"bytes,3,opt,name=status"`
// The patch body. Currently we only support "JSONPatch" which implements RFC 6902.
// +optional
Patch []byte `json:"patch,omitempty" protobuf:"bytes,4,opt,name=patch"`
// The type of Patch. Currently we only allow "JSONPatch".
// +optional
PatchType *PatchType `json:"patchType,omitempty" protobuf:"bytes,5,opt,name=patchType"`
// AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted).
// MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controller will prefix the keys with
// admission webhook name (e.g. imagepolicy.example.com/error=image-blacklisted). AuditAnnotations will be provided by
// the admission webhook to add additional context to the audit log for this request.
// +optional
AuditAnnotations map[string]string `json:"auditAnnotations,omitempty" protobuf:"bytes,6,opt,name=auditAnnotations"`
}
// PatchType is the type of patch being used to represent the mutated object
type PatchType string
// PatchType constants.
const (
PatchTypeJSONPatch PatchType = "JSONPatch"
)
// Operation is the type of resource operation being checked for admission control
type Operation string
// Operation constants
const (
Create Operation = "CREATE"
Update Operation = "UPDATE"
Delete Operation = "DELETE"
Connect Operation = "CONNECT"
)

View File

@ -0,0 +1,77 @@
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v1
// This file contains a collection of methods that can be used from go-restful to
// generate Swagger API documentation for its models. Please read this PR for more
// information on the implementation: https://github.com/emicklei/go-restful/pull/215
//
// TODOs are ignored from the parser (e.g. TODO(andronat):... || TODO:...) if and only if
// they are on one line! For multiple line or blocks that you want to ignore use ---.
// Any context after a --- is ignored.
//
// Those methods can be generated by using hack/update-generated-swagger-docs.sh
// AUTO-GENERATED FUNCTIONS START HERE. DO NOT EDIT.
var map_AdmissionRequest = map[string]string{
"": "AdmissionRequest describes the admission.Attributes for the admission request.",
"uid": "UID is an identifier for the individual request/response. It allows us to distinguish instances of requests which are otherwise identical (parallel requests, requests when earlier requests did not modify etc) The UID is meant to track the round trip (request/response) between the KAS and the WebHook, not the user request. It is suitable for correlating log entries between the webhook and apiserver, for either auditing or debugging.",
"kind": "Kind is the fully-qualified type of object being submitted (for example, v1.Pod or autoscaling.v1.Scale)",
"resource": "Resource is the fully-qualified resource being requested (for example, v1.pods)",
"subResource": "SubResource is the subresource being requested, if any (for example, \"status\" or \"scale\")",
"requestKind": "RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale). If this is specified and differs from the value in \"kind\", an equivalent match and conversion was performed.\n\nFor example, if deployments can be modified via apps/v1 and apps/v1, and a webhook registered a rule of `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]` and `matchPolicy: Equivalent`, an API request to apps/v1 deployments would be converted and sent to the webhook with `kind: {group:\"apps\", version:\"v1\", kind:\"Deployment\"}` (matching the rule the webhook registered for), and `requestKind: {group:\"apps\", version:\"v1\", kind:\"Deployment\"}` (indicating the kind of the original API request).\n\nSee documentation for the \"matchPolicy\" field in the webhook configuration type for more details.",
"requestResource": "RequestResource is the fully-qualified resource of the original API request (for example, v1.pods). If this is specified and differs from the value in \"resource\", an equivalent match and conversion was performed.\n\nFor example, if deployments can be modified via apps/v1 and apps/v1, and a webhook registered a rule of `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]` and `matchPolicy: Equivalent`, an API request to apps/v1 deployments would be converted and sent to the webhook with `resource: {group:\"apps\", version:\"v1\", resource:\"deployments\"}` (matching the resource the webhook registered for), and `requestResource: {group:\"apps\", version:\"v1\", resource:\"deployments\"}` (indicating the resource of the original API request).\n\nSee documentation for the \"matchPolicy\" field in the webhook configuration type.",
"requestSubResource": "RequestSubResource is the name of the subresource of the original API request, if any (for example, \"status\" or \"scale\") If this is specified and differs from the value in \"subResource\", an equivalent match and conversion was performed. See documentation for the \"matchPolicy\" field in the webhook configuration type.",
"name": "Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this field will contain an empty string.",
"namespace": "Namespace is the namespace associated with the request (if any).",
"operation": "Operation is the operation being performed. This may be different than the operation requested. e.g. a patch can result in either a CREATE or UPDATE Operation.",
"userInfo": "UserInfo is information about the requesting user",
"object": "Object is the object from the incoming request.",
"oldObject": "OldObject is the existing object. Only populated for DELETE and UPDATE requests.",
"dryRun": "DryRun indicates that modifications will definitely not be persisted for this request. Defaults to false.",
"options": "Options is the operation option structure of the operation being performed. e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be different than the options the caller provided. e.g. for a patch request the performed Operation might be a CREATE, in which case the Options will a `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`.",
}
func (AdmissionRequest) SwaggerDoc() map[string]string {
return map_AdmissionRequest
}
var map_AdmissionResponse = map[string]string{
"": "AdmissionResponse describes an admission response.",
"uid": "UID is an identifier for the individual request/response. This must be copied over from the corresponding AdmissionRequest.",
"allowed": "Allowed indicates whether or not the admission request was permitted.",
"status": "Result contains extra details into why an admission request was denied. This field IS NOT consulted in any way if \"Allowed\" is \"true\".",
"patch": "The patch body. Currently we only support \"JSONPatch\" which implements RFC 6902.",
"patchType": "The type of Patch. Currently we only allow \"JSONPatch\".",
"auditAnnotations": "AuditAnnotations is an unstructured key value map set by remote admission controller (e.g. error=image-blacklisted). MutatingAdmissionWebhook and ValidatingAdmissionWebhook admission controller will prefix the keys with admission webhook name (e.g. imagepolicy.example.com/error=image-blacklisted). AuditAnnotations will be provided by the admission webhook to add additional context to the audit log for this request.",
}
func (AdmissionResponse) SwaggerDoc() map[string]string {
return map_AdmissionResponse
}
var map_AdmissionReview = map[string]string{
"": "AdmissionReview describes an admission review request/response.",
"request": "Request describes the attributes for the admission request.",
"response": "Response describes the attributes for the admission response.",
}
func (AdmissionReview) SwaggerDoc() map[string]string {
return map_AdmissionReview
}
// AUTO-GENERATED FUNCTIONS END HERE

View File

@ -0,0 +1,136 @@
// +build !ignore_autogenerated
/*
Copyright The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
// Code generated by deepcopy-gen. DO NOT EDIT.
package v1
import (
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
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 *AdmissionRequest) DeepCopyInto(out *AdmissionRequest) {
*out = *in
out.Kind = in.Kind
out.Resource = in.Resource
if in.RequestKind != nil {
in, out := &in.RequestKind, &out.RequestKind
*out = new(metav1.GroupVersionKind)
**out = **in
}
if in.RequestResource != nil {
in, out := &in.RequestResource, &out.RequestResource
*out = new(metav1.GroupVersionResource)
**out = **in
}
in.UserInfo.DeepCopyInto(&out.UserInfo)
in.Object.DeepCopyInto(&out.Object)
in.OldObject.DeepCopyInto(&out.OldObject)
if in.DryRun != nil {
in, out := &in.DryRun, &out.DryRun
*out = new(bool)
**out = **in
}
in.Options.DeepCopyInto(&out.Options)
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionRequest.
func (in *AdmissionRequest) DeepCopy() *AdmissionRequest {
if in == nil {
return nil
}
out := new(AdmissionRequest)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionResponse) DeepCopyInto(out *AdmissionResponse) {
*out = *in
if in.Result != nil {
in, out := &in.Result, &out.Result
*out = new(metav1.Status)
(*in).DeepCopyInto(*out)
}
if in.Patch != nil {
in, out := &in.Patch, &out.Patch
*out = make([]byte, len(*in))
copy(*out, *in)
}
if in.PatchType != nil {
in, out := &in.PatchType, &out.PatchType
*out = new(PatchType)
**out = **in
}
if in.AuditAnnotations != nil {
in, out := &in.AuditAnnotations, &out.AuditAnnotations
*out = make(map[string]string, len(*in))
for key, val := range *in {
(*out)[key] = val
}
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionResponse.
func (in *AdmissionResponse) DeepCopy() *AdmissionResponse {
if in == nil {
return nil
}
out := new(AdmissionResponse)
in.DeepCopyInto(out)
return out
}
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
func (in *AdmissionReview) DeepCopyInto(out *AdmissionReview) {
*out = *in
out.TypeMeta = in.TypeMeta
if in.Request != nil {
in, out := &in.Request, &out.Request
*out = new(AdmissionRequest)
(*in).DeepCopyInto(*out)
}
if in.Response != nil {
in, out := &in.Response, &out.Response
*out = new(AdmissionResponse)
(*in).DeepCopyInto(*out)
}
return
}
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new AdmissionReview.
func (in *AdmissionReview) DeepCopy() *AdmissionReview {
if in == nil {
return nil
}
out := new(AdmissionReview)
in.DeepCopyInto(out)
return out
}
// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object.
func (in *AdmissionReview) DeepCopyObject() runtime.Object {
if c := in.DeepCopy(); c != nil {
return c
}
return nil
}

View File

@ -80,7 +80,7 @@ message AdmissionRequest {
optional string requestSubResource = 15;
// Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and
// rely on the server to generate the name. If that is the case, this method will return the empty string.
// rely on the server to generate the name. If that is the case, this field will contain an empty string.
// +optional
optional string name = 5;
@ -95,11 +95,11 @@ message AdmissionRequest {
// UserInfo is information about the requesting user
optional k8s.io.api.authentication.v1.UserInfo userInfo = 8;
// Object is the object from the incoming request prior to default values being applied
// Object is the object from the incoming request.
// +optional
optional k8s.io.apimachinery.pkg.runtime.RawExtension object = 9;
// OldObject is the existing object. Only populated for UPDATE requests.
// OldObject is the existing object. Only populated for DELETE and UPDATE requests.
// +optional
optional k8s.io.apimachinery.pkg.runtime.RawExtension oldObject = 10;

View File

@ -82,7 +82,7 @@ type AdmissionRequest struct {
RequestSubResource string `json:"requestSubResource,omitempty" protobuf:"bytes,15,opt,name=requestSubResource"`
// Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and
// rely on the server to generate the name. If that is the case, this method will return the empty string.
// rely on the server to generate the name. If that is the case, this field will contain an empty string.
// +optional
Name string `json:"name,omitempty" protobuf:"bytes,5,opt,name=name"`
// Namespace is the namespace associated with the request (if any).
@ -93,10 +93,10 @@ type AdmissionRequest struct {
Operation Operation `json:"operation" protobuf:"bytes,7,opt,name=operation"`
// UserInfo is information about the requesting user
UserInfo authenticationv1.UserInfo `json:"userInfo" protobuf:"bytes,8,opt,name=userInfo"`
// Object is the object from the incoming request prior to default values being applied
// Object is the object from the incoming request.
// +optional
Object runtime.RawExtension `json:"object,omitempty" protobuf:"bytes,9,opt,name=object"`
// OldObject is the existing object. Only populated for UPDATE requests.
// OldObject is the existing object. Only populated for DELETE and UPDATE requests.
// +optional
OldObject runtime.RawExtension `json:"oldObject,omitempty" protobuf:"bytes,10,opt,name=oldObject"`
// DryRun indicates that modifications will definitely not be persisted for this request.

View File

@ -36,12 +36,12 @@ var map_AdmissionRequest = map[string]string{
"requestKind": "RequestKind is the fully-qualified type of the original API request (for example, v1.Pod or autoscaling.v1.Scale). If this is specified and differs from the value in \"kind\", an equivalent match and conversion was performed.\n\nFor example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]` and `matchPolicy: Equivalent`, an API request to apps/v1beta1 deployments would be converted and sent to the webhook with `kind: {group:\"apps\", version:\"v1\", kind:\"Deployment\"}` (matching the rule the webhook registered for), and `requestKind: {group:\"apps\", version:\"v1beta1\", kind:\"Deployment\"}` (indicating the kind of the original API request).\n\nSee documentation for the \"matchPolicy\" field in the webhook configuration type for more details.",
"requestResource": "RequestResource is the fully-qualified resource of the original API request (for example, v1.pods). If this is specified and differs from the value in \"resource\", an equivalent match and conversion was performed.\n\nFor example, if deployments can be modified via apps/v1 and apps/v1beta1, and a webhook registered a rule of `apiGroups:[\"apps\"], apiVersions:[\"v1\"], resources: [\"deployments\"]` and `matchPolicy: Equivalent`, an API request to apps/v1beta1 deployments would be converted and sent to the webhook with `resource: {group:\"apps\", version:\"v1\", resource:\"deployments\"}` (matching the resource the webhook registered for), and `requestResource: {group:\"apps\", version:\"v1beta1\", resource:\"deployments\"}` (indicating the resource of the original API request).\n\nSee documentation for the \"matchPolicy\" field in the webhook configuration type.",
"requestSubResource": "RequestSubResource is the name of the subresource of the original API request, if any (for example, \"status\" or \"scale\") If this is specified and differs from the value in \"subResource\", an equivalent match and conversion was performed. See documentation for the \"matchPolicy\" field in the webhook configuration type.",
"name": "Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this method will return the empty string.",
"name": "Name is the name of the object as presented in the request. On a CREATE operation, the client may omit name and rely on the server to generate the name. If that is the case, this field will contain an empty string.",
"namespace": "Namespace is the namespace associated with the request (if any).",
"operation": "Operation is the operation being performed. This may be different than the operation requested. e.g. a patch can result in either a CREATE or UPDATE Operation.",
"userInfo": "UserInfo is information about the requesting user",
"object": "Object is the object from the incoming request prior to default values being applied",
"oldObject": "OldObject is the existing object. Only populated for UPDATE requests.",
"object": "Object is the object from the incoming request.",
"oldObject": "OldObject is the existing object. Only populated for DELETE and UPDATE requests.",
"dryRun": "DryRun indicates that modifications will definitely not be persisted for this request. Defaults to false.",
"options": "Options is the operation option structure of the operation being performed. e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be different than the options the caller provided. e.g. for a patch request the performed Operation might be a CREATE, in which case the Options will a `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`.",
}

View File

@ -20,6 +20,7 @@ import (
"math/rand"
"testing"
admissionv1 "k8s.io/api/admission/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregv1 "k8s.io/api/admissionregistration/v1"
admissionregv1beta1 "k8s.io/api/admissionregistration/v1beta1"
@ -69,6 +70,7 @@ import (
var groups = []runtime.SchemeBuilder{
admissionv1beta1.SchemeBuilder,
admissionv1.SchemeBuilder,
admissionregv1beta1.SchemeBuilder,
admissionregv1.SchemeBuilder,
appsv1beta1.SchemeBuilder,

View File

@ -0,0 +1,81 @@
{
"kind": "AdmissionReview",
"apiVersion": "admission.k8s.io/v1",
"request": {
"uid": "ő岅ȕHH壬%龺ǟ橸章蒪ʤǎ",
"kind": {
"group": "2",
"version": "3",
"kind": "4"
},
"resource": {
"group": "5",
"version": "6",
"resource": "7"
},
"subResource": "8",
"requestKind": {
"group": "9",
"version": "10",
"kind": "11"
},
"requestResource": {
"group": "12",
"version": "13",
"resource": "14"
},
"requestSubResource": "15",
"name": "16",
"namespace": "17",
"operation": ıŵDz",
"userInfo": {
"username": "18",
"uid": "19",
"groups": [
"20"
],
"extra": {
"21": [
"22"
]
}
},
"object": {"apiVersion":"example.com/v1","kind":"CustomType","spec":{"replicas":1},"status":{"available":1}},
"oldObject": {"apiVersion":"example.com/v1","kind":"CustomType","spec":{"replicas":1},"status":{"available":1}},
"dryRun": false,
"options": {"apiVersion":"example.com/v1","kind":"CustomType","spec":{"replicas":1},"status":{"available":1}}
},
"response": {
"uid": "輂]¨\u0026桰]]æȌ殸2爟¼ªov鈶",
"allowed": false,
"status": {
"metadata": {
"selfLink": "(湗Ć]ʪƬ滈憴Uą飋ī",
"resourceVersion": "71208173751669476"
},
"status": "23",
"message": "24",
"reason": "Hr鯹)晿\u003co,c鮽ort昍řČ扷5Ɨ",
"details": {
"name": "25",
"group": "26",
"kind": "27",
"uid": "Ƣ6/ʕVŚ(ĿȊ甞",
"causes": [
{
"reason": "颋Dž",
"message": "28",
"field": "29"
}
],
"retryAfterSeconds": 1001983654
},
"code": 153738858
},
"patch": "fQ==",
"patchType": "-Ǐ忄*齧獚敆Ȏțê",
"auditAnnotations": {
"30": "31"
}
}
}

View File

@ -0,0 +1,80 @@
apiVersion: admission.k8s.io/v1
kind: AdmissionReview
request:
dryRun: false
kind:
group: "2"
kind: "4"
version: "3"
name: "16"
namespace: "17"
object:
apiVersion: example.com/v1
kind: CustomType
spec:
replicas: 1
status:
available: 1
oldObject:
apiVersion: example.com/v1
kind: CustomType
spec:
replicas: 1
status:
available: 1
operation: ¡ıŵDz
options:
apiVersion: example.com/v1
kind: CustomType
spec:
replicas: 1
status:
available: 1
requestKind:
group: "9"
kind: "11"
version: "10"
requestResource:
group: "12"
resource: "14"
version: "13"
requestSubResource: "15"
resource:
group: "5"
resource: "7"
version: "6"
subResource: "8"
uid: ő岅ȕHH壬%龺ǟ橸章蒪ʤǎ
userInfo:
extra:
"21":
- "22"
groups:
- "20"
uid: "19"
username: "18"
response:
allowed: false
auditAnnotations:
"30": "31"
patch: fQ==
patchType: -Ǐ忄*齧獚敆Ȏțê
status:
code: 153738858
details:
causes:
- field: "29"
message: "28"
reason: 颋Dž
group: "26"
kind: "27"
name: "25"
retryAfterSeconds: 1001983654
uid: Ƣ6/ʕVŚ(ĿȊ甞
message: "24"
metadata:
resourceVersion: "71208173751669476"
selfLink: (湗Ć]ʪƬ滈憴Uą飋ī
reason: Hr鯹)晿<o,c鮽ort昍řČ扷5Ɨ
status: "23"
uid: 輂]¨&桰]]æȌ殸2爟¼ªov鈶

View File

@ -298,7 +298,7 @@ func validateConversionReviewVersions(versions []string, requireRecognizedVersio
if requireRecognizedVersion && !hasAcceptedVersion {
allErrs = append(allErrs, field.Invalid(
fldPath, versions,
fmt.Sprintf("none of the versions accepted by this server. accepted version(s) are %v",
fmt.Sprintf("must include at least one of %v",
strings.Join(acceptedConversionReviewVersion, ", "))))
}
}

View File

@ -40,7 +40,7 @@ type webhookConverterFactory struct {
}
func newWebhookConverterFactory(serviceResolver webhook.ServiceResolver, authResolverWrapper webhook.AuthenticationInfoResolverWrapper) (*webhookConverterFactory, error) {
clientManager, err := webhook.NewClientManager(v1beta1.SchemeGroupVersion, v1beta1.AddToScheme)
clientManager, err := webhook.NewClientManager([]schema.GroupVersion{v1beta1.SchemeGroupVersion}, v1beta1.AddToScheme)
if err != nil {
return nil, err
}

View File

@ -11,6 +11,7 @@ go_library(
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/generic",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/admission/v1:go_default_library",
"//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/errors:go_default_library",

View File

@ -21,6 +21,7 @@ import (
"fmt"
"io"
admissionv1 "k8s.io/api/admission/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
@ -65,7 +66,14 @@ func NewWebhook(handler *admission.Handler, configFile io.Reader, sourceFactory
return nil, err
}
cm, err := webhookutil.NewClientManager(admissionv1beta1.SchemeGroupVersion, admissionv1beta1.AddToScheme)
cm, err := webhookutil.NewClientManager(
[]schema.GroupVersion{
admissionv1beta1.SchemeGroupVersion,
admissionv1.SchemeGroupVersion,
},
admissionv1beta1.AddToScheme,
admissionv1.AddToScheme,
)
if err != nil {
return nil, err
}

View File

@ -12,7 +12,7 @@ go_library(
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/mutating",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/api/admission/v1: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",

View File

@ -27,7 +27,7 @@ import (
apiequality "k8s.io/apimachinery/pkg/api/equality"
"k8s.io/klog"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionv1 "k8s.io/api/admission/v1"
"k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
@ -39,7 +39,7 @@ import (
"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"
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
webhookutil "k8s.io/apiserver/pkg/util/webhook"
)
@ -163,20 +163,16 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
}
}
// Currently dispatcher only supports `v1beta1` AdmissionReview
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, invocation.Webhook) {
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReview")}
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
if err != nil {
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
// Make the webhook request
request := request.CreateAdmissionReview(attr, invocation)
client, err := a.cm.HookClient(util.HookClientConfigForWebhook(invocation.Webhook))
if err != nil {
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
response := &admissionv1beta1.AdmissionReview{}
r := client.Post().Context(ctx).Body(&request)
r := client.Post().Context(ctx).Body(request)
if h.TimeoutSeconds != nil {
r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second)
}
@ -184,26 +180,26 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
if response.Response == nil {
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
result, err := webhookrequest.VerifyAdmissionResponse(uid, true, response)
if err != nil {
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
for k, v := range response.Response.AuditAnnotations {
for k, v := range result.AuditAnnotations {
key := h.Name + "/" + k
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
klog.Warningf("Failed to set admission audit annotation %s to %s for mutating webhook %s: %v", key, v, h.Name, err)
}
}
if !response.Response.Allowed {
return false, webhookerrors.ToStatusErr(h.Name, response.Response.Result)
if !result.Allowed {
return false, webhookerrors.ToStatusErr(h.Name, result.Result)
}
patchJS := response.Response.Patch
if len(patchJS) == 0 {
if len(result.Patch) == 0 {
return false, nil
}
patchObj, err := jsonpatch.DecodePatch(patchJS)
patchObj, err := jsonpatch.DecodePatch(result.Patch)
if err != nil {
return false, apierrors.NewInternalError(err)
}
@ -216,14 +212,21 @@ func (a *mutatingDispatcher) callAttrMutatingHook(ctx context.Context, h *v1beta
return false, apierrors.NewInternalError(fmt.Errorf("admission webhook %q attempted to modify the object, which is not supported for this operation", h.Name))
}
var patchedJS []byte
jsonSerializer := json.NewSerializer(json.DefaultMetaFactory, o.GetObjectCreater(), o.GetObjectTyper(), false)
objJS, err := runtime.Encode(jsonSerializer, attr.VersionedObject)
if err != nil {
return false, apierrors.NewInternalError(err)
}
patchedJS, err := patchObj.Apply(objJS)
if err != nil {
return false, apierrors.NewInternalError(err)
switch result.PatchType {
// VerifyAdmissionResponse normalizes to v1 patch types, regardless of the AdmissionReview version used
case admissionv1.PatchTypeJSONPatch:
objJS, err := runtime.Encode(jsonSerializer, attr.VersionedObject)
if err != nil {
return false, apierrors.NewInternalError(err)
}
patchedJS, err = patchObj.Apply(objJS)
if err != nil {
return false, apierrors.NewInternalError(err)
}
default:
return false, &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("unsupported patch type %q", result.PatchType)}
}
var newVersionedObject runtime.Object

View File

@ -1,4 +1,4 @@
load("@io_bazel_rules_go//go:def.bzl", "go_library")
load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test")
go_library(
name = "go_default_library",
@ -10,10 +10,12 @@ go_library(
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/request",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/admission/v1:go_default_library",
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/api/authentication/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/util/uuid:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/generic:go_default_library",
],
@ -32,3 +34,26 @@ filegroup(
tags = ["automanaged"],
visibility = ["//visibility:public"],
)
go_test(
name = "go_default_test",
srcs = ["admissionreview_test.go"],
embed = [":go_default_library"],
deps = [
"//staging/src/k8s.io/api/admission/v1:go_default_library",
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/authentication/v1:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/apis/meta/v1: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/types: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/generic:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/authentication/user:go_default_library",
"//vendor/github.com/google/go-cmp/cmp:go_default_library",
"//vendor/k8s.io/utils/pointer:go_default_library",
],
)

View File

@ -17,16 +17,138 @@ limitations under the License.
package request
import (
"fmt"
admissionv1 "k8s.io/api/admission/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apimachinery/pkg/util/uuid"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
)
// CreateAdmissionReview creates an AdmissionReview for the provided admission.Attributes
func CreateAdmissionReview(versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) admissionv1beta1.AdmissionReview {
// AdmissionResponse contains the fields extracted from an AdmissionReview response
type AdmissionResponse struct {
AuditAnnotations map[string]string
Allowed bool
Patch []byte
PatchType admissionv1.PatchType
Result *metav1.Status
}
// VerifyAdmissionResponse checks the validity of the provided admission review object, and returns the
// audit annotations, whether the response allowed the request, any provided patch/patchType/status,
// or an error if the provided admission review was not valid.
func VerifyAdmissionResponse(uid types.UID, mutating bool, review runtime.Object) (*AdmissionResponse, error) {
switch r := review.(type) {
case *admissionv1.AdmissionReview:
if r.Response == nil {
return nil, fmt.Errorf("webhook response was absent")
}
// Verify UID matches
if r.Response.UID != uid {
return nil, fmt.Errorf("expected response.uid=%q, got %q", uid, r.Response.UID)
}
// Verify GVK
v1GVK := admissionv1.SchemeGroupVersion.WithKind("AdmissionReview")
if r.GroupVersionKind() != v1GVK {
return nil, fmt.Errorf("expected webhook response of %v, got %v", v1GVK.String(), r.GroupVersionKind().String())
}
patch := []byte(nil)
patchType := admissionv1.PatchType("")
if mutating {
// Ensure a mutating webhook provides both patch and patchType together
if len(r.Response.Patch) > 0 && r.Response.PatchType == nil {
return nil, fmt.Errorf("webhook returned response.patch but not response.patchType")
}
if len(r.Response.Patch) == 0 && r.Response.PatchType != nil {
return nil, fmt.Errorf("webhook returned response.patchType but not response.patch")
}
patch = r.Response.Patch
if r.Response.PatchType != nil {
patchType = *r.Response.PatchType
if len(patchType) == 0 {
return nil, fmt.Errorf("webhook returned invalid response.patchType of %q", patchType)
}
}
} else {
// Ensure a validating webhook doesn't return patch or patchType
if len(r.Response.Patch) > 0 {
return nil, fmt.Errorf("validating webhook may not return response.patch")
}
if r.Response.PatchType != nil {
return nil, fmt.Errorf("validating webhook may not return response.patchType")
}
}
return &AdmissionResponse{
AuditAnnotations: r.Response.AuditAnnotations,
Allowed: r.Response.Allowed,
Patch: patch,
PatchType: patchType,
Result: r.Response.Result,
}, nil
case *admissionv1beta1.AdmissionReview:
if r.Response == nil {
return nil, fmt.Errorf("webhook response was absent")
}
// Response GVK and response.uid were not verified in v1beta1 handling, allow any
patch := []byte(nil)
patchType := admissionv1.PatchType("")
if mutating {
patch = r.Response.Patch
if len(r.Response.Patch) > 0 {
// patch type was not verified in v1beta1 admissionreview handling. pin to only supported version if a patch is provided.
patchType = admissionv1.PatchTypeJSONPatch
}
}
return &AdmissionResponse{
AuditAnnotations: r.Response.AuditAnnotations,
Allowed: r.Response.Allowed,
Patch: patch,
PatchType: patchType,
Result: r.Response.Result,
}, nil
default:
return nil, fmt.Errorf("unexpected response type %T", review)
}
}
// CreateAdmissionObjects returns the unique request uid, the AdmissionReview object to send the webhook and to decode the response into,
// or an error if the webhook does not support receiving any of the admission review versions we know to send
func CreateAdmissionObjects(versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) (uid types.UID, request, response runtime.Object, err error) {
for _, version := range invocation.Webhook.GetAdmissionReviewVersions() {
switch version {
case admissionv1.SchemeGroupVersion.Version:
uid := types.UID(uuid.NewUUID())
request := CreateV1AdmissionReview(uid, versionedAttributes, invocation)
response := &admissionv1.AdmissionReview{}
return uid, request, response, nil
case admissionv1beta1.SchemeGroupVersion.Version:
uid := types.UID(uuid.NewUUID())
request := CreateV1beta1AdmissionReview(uid, versionedAttributes, invocation)
response := &admissionv1beta1.AdmissionReview{}
return uid, request, response, nil
}
}
return "", nil, nil, fmt.Errorf("webhook does not accept known AdmissionReview versions (v1, v1beta1)")
}
// CreateV1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
func CreateV1AdmissionReview(uid types.UID, versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1.AdmissionReview {
attr := versionedAttributes.Attributes
gvk := invocation.Kind
gvr := invocation.Resource
@ -48,9 +170,75 @@ func CreateAdmissionReview(versionedAttributes *generic.VersionedAttributes, inv
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
}
return admissionv1beta1.AdmissionReview{
return &admissionv1.AdmissionReview{
Request: &admissionv1.AdmissionRequest{
UID: uid,
Kind: metav1.GroupVersionKind{
Group: gvk.Group,
Kind: gvk.Kind,
Version: gvk.Version,
},
Resource: metav1.GroupVersionResource{
Group: gvr.Group,
Resource: gvr.Resource,
Version: gvr.Version,
},
SubResource: subresource,
RequestKind: &metav1.GroupVersionKind{
Group: requestGVK.Group,
Kind: requestGVK.Kind,
Version: requestGVK.Version,
},
RequestResource: &metav1.GroupVersionResource{
Group: requestGVR.Group,
Resource: requestGVR.Resource,
Version: requestGVR.Version,
},
RequestSubResource: requestSubResource,
Name: attr.GetName(),
Namespace: attr.GetNamespace(),
Operation: admissionv1.Operation(attr.GetOperation()),
UserInfo: userInfo,
Object: runtime.RawExtension{
Object: versionedAttributes.VersionedObject,
},
OldObject: runtime.RawExtension{
Object: versionedAttributes.VersionedOldObject,
},
DryRun: &dryRun,
Options: runtime.RawExtension{
Object: attr.GetOperationOptions(),
},
},
}
}
// CreateV1beta1AdmissionReview creates an AdmissionReview for the provided admission.Attributes
func CreateV1beta1AdmissionReview(uid types.UID, versionedAttributes *generic.VersionedAttributes, invocation *generic.WebhookInvocation) *admissionv1beta1.AdmissionReview {
attr := versionedAttributes.Attributes
gvk := invocation.Kind
gvr := invocation.Resource
subresource := invocation.Subresource
requestGVK := attr.GetKind()
requestGVR := attr.GetResource()
requestSubResource := attr.GetSubresource()
aUserInfo := attr.GetUserInfo()
userInfo := authenticationv1.UserInfo{
Extra: make(map[string]authenticationv1.ExtraValue),
Groups: aUserInfo.GetGroups(),
UID: aUserInfo.GetUID(),
Username: aUserInfo.GetName(),
}
dryRun := attr.IsDryRun()
// Convert the extra information in the user object
for key, val := range aUserInfo.GetExtra() {
userInfo.Extra[key] = authenticationv1.ExtraValue(val)
}
return &admissionv1beta1.AdmissionReview{
Request: &admissionv1beta1.AdmissionRequest{
UID: uuid.NewUUID(),
UID: uid,
Kind: metav1.GroupVersionKind{
Group: gvk.Group,
Kind: gvk.Kind,

View File

@ -0,0 +1,618 @@
/*
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 request
import (
"reflect"
"strings"
"testing"
"github.com/google/go-cmp/cmp"
admissionv1 "k8s.io/api/admission/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
appsv1 "k8s.io/api/apps/v1"
authenticationv1 "k8s.io/api/authentication/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/admission/plugin/webhook"
"k8s.io/apiserver/pkg/admission/plugin/webhook/generic"
"k8s.io/apiserver/pkg/authentication/user"
utilpointer "k8s.io/utils/pointer"
)
func TestVerifyAdmissionResponse(t *testing.T) {
v1beta1JSONPatch := admissionv1beta1.PatchTypeJSONPatch
v1JSONPatch := admissionv1.PatchTypeJSONPatch
emptyv1beta1Patch := admissionv1beta1.PatchType("")
emptyv1Patch := admissionv1.PatchType("")
invalidv1beta1Patch := admissionv1beta1.PatchType("Foo")
invalidv1Patch := admissionv1.PatchType("Foo")
testcases := []struct {
name string
uid types.UID
mutating bool
review runtime.Object
expectAuditAnnotations map[string]string
expectAllowed bool
expectPatch []byte
expectPatchType admissionv1.PatchType
expectResult *metav1.Status
expectErr string
}{
// Allowed validating
{
name: "v1beta1 allowed validating",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{Allowed: true},
},
expectAllowed: true,
},
{
name: "v1 allowed validating",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{UID: "123", Allowed: true},
},
expectAllowed: true,
},
// Allowed mutating
{
name: "v1beta1 allowed mutating",
uid: "123",
mutating: true,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{Allowed: true},
},
expectAllowed: true,
},
{
name: "v1 allowed mutating",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{UID: "123", Allowed: true},
},
expectAllowed: true,
},
// Audit annotations
{
name: "v1beta1 auditAnnotations",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
AuditAnnotations: map[string]string{"foo": "bar"},
},
},
expectAllowed: true,
expectAuditAnnotations: map[string]string{"foo": "bar"},
},
{
name: "v1 auditAnnotations",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
AuditAnnotations: map[string]string{"foo": "bar"},
},
},
expectAllowed: true,
expectAuditAnnotations: map[string]string{"foo": "bar"},
},
// Patch
{
name: "v1beta1 patch",
uid: "123",
mutating: true,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: "JSONPatch",
},
{
name: "v1 patch",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &v1JSONPatch,
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: "JSONPatch",
},
// Result
{
name: "v1beta1 result",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: false,
Result: &metav1.Status{Status: "Failure", Message: "Foo", Code: 401},
},
},
expectAllowed: false,
expectResult: &metav1.Status{Status: "Failure", Message: "Foo", Code: 401},
},
{
name: "v1 result",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: false,
Result: &metav1.Status{Status: "Failure", Message: "Foo", Code: 401},
},
},
expectAllowed: false,
expectResult: &metav1.Status{Status: "Failure", Message: "Foo", Code: 401},
},
// Missing response
{
name: "v1beta1 no response",
uid: "123",
review: &admissionv1beta1.AdmissionReview{},
expectErr: "response was absent",
},
{
name: "v1 no response",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
},
expectErr: "response was absent",
},
// v1 invalid responses
{
name: "v1 wrong group",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io2/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
},
},
expectErr: "expected webhook response of admission.k8s.io/v1, Kind=AdmissionReview",
},
{
name: "v1 wrong version",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v2", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
},
},
expectErr: "expected webhook response of admission.k8s.io/v1, Kind=AdmissionReview",
},
{
name: "v1 wrong kind",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview2"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
},
},
expectErr: "expected webhook response of admission.k8s.io/v1, Kind=AdmissionReview",
},
{
name: "v1 wrong uid",
uid: "123",
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "1234",
Allowed: true,
},
},
expectErr: `expected response.uid="123"`,
},
{
name: "v1 patch without patch type",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
},
},
expectErr: `webhook returned response.patch but not response.patchType`,
},
{
name: "v1 patch type without patch",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
PatchType: &v1JSONPatch,
},
},
expectErr: `webhook returned response.patchType but not response.patch`,
},
{
name: "v1 empty patch type",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &emptyv1Patch,
},
},
expectErr: `webhook returned invalid response.patchType of ""`,
},
{
name: "v1 invalid patch type",
uid: "123",
mutating: true,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &invalidv1Patch,
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: invalidv1Patch, // invalid patch types are caught when the mutating dispatcher evaluates the patch
},
{
name: "v1 patch for validating webhook",
uid: "123",
mutating: false,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
},
},
expectErr: `validating webhook may not return response.patch`,
},
{
name: "v1 patch type for validating webhook",
uid: "123",
mutating: false,
review: &admissionv1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io/v1", Kind: "AdmissionReview"},
Response: &admissionv1.AdmissionResponse{
UID: "123",
Allowed: true,
PatchType: &invalidv1Patch,
},
},
expectErr: `validating webhook may not return response.patchType`,
},
// v1beta1 invalid responses that we have to allow/fixup for compatibility
{
name: "v1beta1 wrong group/version/kind",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
TypeMeta: metav1.TypeMeta{APIVersion: "admission.k8s.io2/v2", Kind: "AdmissionReview2"},
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
},
},
expectAllowed: true,
},
{
name: "v1beta1 wrong uid",
uid: "123",
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
UID: "1234",
Allowed: true,
},
},
expectAllowed: true,
},
{
name: "v1beta1 validating returns patch/patchType",
uid: "123",
mutating: false,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
UID: "1234",
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &v1beta1JSONPatch,
},
},
expectAllowed: true,
},
{
name: "v1beta1 empty patch type",
uid: "123",
mutating: true,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &emptyv1beta1Patch,
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: admissionv1.PatchTypeJSONPatch,
},
{
name: "v1beta1 invalid patchType",
uid: "123",
mutating: true,
review: &admissionv1beta1.AdmissionReview{
Response: &admissionv1beta1.AdmissionResponse{
Allowed: true,
Patch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
PatchType: &invalidv1beta1Patch,
},
},
expectAllowed: true,
expectPatch: []byte(`[{"op":"add","path":"/foo","value":"bar"}]`),
expectPatchType: admissionv1.PatchTypeJSONPatch,
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
result, err := VerifyAdmissionResponse(tc.uid, tc.mutating, tc.review)
if err != nil {
if len(tc.expectErr) > 0 {
if !strings.Contains(err.Error(), tc.expectErr) {
t.Errorf("expected error '%s', got %v", tc.expectErr, err)
}
} else {
t.Errorf("unexpected error %v", err)
}
return
} else if len(tc.expectErr) > 0 {
t.Errorf("expected error '%s', got none", tc.expectErr)
return
}
if e, a := tc.expectAuditAnnotations, result.AuditAnnotations; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectAllowed, result.Allowed; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectPatch, result.Patch; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectPatchType, result.PatchType; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectResult, result.Result; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
})
}
}
func TestCreateAdmissionObjects(t *testing.T) {
internalObj := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "2", Name: "myname", Namespace: "myns"}}
internalObjOld := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "1", Name: "myname", Namespace: "myns"}}
versionedObj := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "2", Name: "myname", Namespace: "myns"}}
versionedObjOld := &appsv1.Deployment{ObjectMeta: metav1.ObjectMeta{ResourceVersion: "1", Name: "myname", Namespace: "myns"}}
userInfo := &user.DefaultInfo{
Name: "myuser",
Groups: []string{"mygroup"},
UID: "myuid",
Extra: map[string][]string{"extrakey": {"value1", "value2"}},
}
attrs := admission.NewAttributesRecord(
internalObj.DeepCopyObject(),
internalObjOld.DeepCopyObject(),
schema.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
"myns",
"myname",
schema.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
"",
admission.Update,
&metav1.UpdateOptions{FieldManager: "foo"},
false,
userInfo,
)
testcases := []struct {
name string
attrs *generic.VersionedAttributes
invocation *generic.WebhookInvocation
expectRequest func(uid types.UID) runtime.Object
expectResponse runtime.Object
expectErr string
}{
{
name: "no supported versions",
invocation: &generic.WebhookInvocation{
Webhook: webhook.NewMutatingWebhookAccessor("mywebhook", &admissionregistrationv1beta1.MutatingWebhook{}),
},
expectErr: "webhook does not accept known AdmissionReview versions",
},
{
name: "no known supported versions",
invocation: &generic.WebhookInvocation{
Webhook: webhook.NewMutatingWebhookAccessor("mywebhook", &admissionregistrationv1beta1.MutatingWebhook{
AdmissionReviewVersions: []string{"vX"},
}),
},
expectErr: "webhook does not accept known AdmissionReview versions",
},
{
name: "v1",
attrs: &generic.VersionedAttributes{
VersionedObject: versionedObj.DeepCopyObject(),
VersionedOldObject: versionedObjOld.DeepCopyObject(),
Attributes: attrs,
},
invocation: &generic.WebhookInvocation{
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
Subresource: "",
Kind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"},
Webhook: webhook.NewMutatingWebhookAccessor("mywebhook", &admissionregistrationv1beta1.MutatingWebhook{
AdmissionReviewVersions: []string{"v1", "v1beta1"},
}),
},
expectRequest: func(uid types.UID) runtime.Object {
return &admissionv1.AdmissionReview{
Request: &admissionv1.AdmissionRequest{
UID: uid,
Kind: metav1.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"},
Resource: metav1.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
SubResource: "",
RequestKind: &metav1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
RequestResource: &metav1.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
RequestSubResource: "",
Name: "myname",
Namespace: "myns",
Operation: "UPDATE",
UserInfo: authenticationv1.UserInfo{
Username: "myuser",
UID: "myuid",
Groups: []string{"mygroup"},
Extra: map[string]authenticationv1.ExtraValue{"extrakey": {"value1", "value2"}},
},
Object: runtime.RawExtension{Object: versionedObj},
OldObject: runtime.RawExtension{Object: versionedObjOld},
DryRun: utilpointer.BoolPtr(false),
Options: runtime.RawExtension{Object: &metav1.UpdateOptions{FieldManager: "foo"}},
},
}
},
expectResponse: &admissionv1.AdmissionReview{},
},
{
name: "v1beta1",
attrs: &generic.VersionedAttributes{
VersionedObject: versionedObj.DeepCopyObject(),
VersionedOldObject: versionedObjOld.DeepCopyObject(),
Attributes: attrs,
},
invocation: &generic.WebhookInvocation{
Resource: schema.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
Subresource: "",
Kind: schema.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"},
Webhook: webhook.NewMutatingWebhookAccessor("mywebhook", &admissionregistrationv1beta1.MutatingWebhook{
AdmissionReviewVersions: []string{"v1beta1", "v1"},
}),
},
expectRequest: func(uid types.UID) runtime.Object {
return &admissionv1beta1.AdmissionReview{
Request: &admissionv1beta1.AdmissionRequest{
UID: uid,
Kind: metav1.GroupVersionKind{Group: "extensions", Version: "v1beta1", Kind: "Deployment"},
Resource: metav1.GroupVersionResource{Group: "extensions", Version: "v1beta1", Resource: "deployments"},
SubResource: "",
RequestKind: &metav1.GroupVersionKind{Group: "apps", Version: "v1", Kind: "Deployment"},
RequestResource: &metav1.GroupVersionResource{Group: "apps", Version: "v1", Resource: "deployments"},
RequestSubResource: "",
Name: "myname",
Namespace: "myns",
Operation: "UPDATE",
UserInfo: authenticationv1.UserInfo{
Username: "myuser",
UID: "myuid",
Groups: []string{"mygroup"},
Extra: map[string]authenticationv1.ExtraValue{"extrakey": {"value1", "value2"}},
},
Object: runtime.RawExtension{Object: versionedObj},
OldObject: runtime.RawExtension{Object: versionedObjOld},
DryRun: utilpointer.BoolPtr(false),
Options: runtime.RawExtension{Object: &metav1.UpdateOptions{FieldManager: "foo"}},
},
}
},
expectResponse: &admissionv1beta1.AdmissionReview{},
},
}
for _, tc := range testcases {
t.Run(tc.name, func(t *testing.T) {
uid, request, response, err := CreateAdmissionObjects(tc.attrs, tc.invocation)
if err != nil {
if len(tc.expectErr) > 0 {
if !strings.Contains(err.Error(), tc.expectErr) {
t.Errorf("expected error '%s', got %v", tc.expectErr, err)
}
} else {
t.Errorf("unexpected error %v", err)
}
return
} else if len(tc.expectErr) > 0 {
t.Errorf("expected error '%s', got none", tc.expectErr)
return
}
if len(uid) == 0 {
t.Errorf("expected uid, got none")
}
if e, a := tc.expectRequest(uid), request; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
if e, a := tc.expectResponse, response; !reflect.DeepEqual(e, a) {
t.Errorf("unexpected: %v", cmp.Diff(e, a))
}
})
}
}

View File

@ -487,7 +487,7 @@ func NewNonMutatingTestCases(url *url.URL) []ValidatingTest {
AdmissionReviewVersions: []string{"v1beta1"},
}},
ExpectStatusCode: http.StatusInternalServerError,
ErrorContains: "Webhook response was absent",
ErrorContains: "webhook response was absent",
},
{
Name: "no match dry run",

View File

@ -11,7 +11,6 @@ go_library(
importpath = "k8s.io/apiserver/pkg/admission/plugin/webhook/validating",
visibility = ["//visibility:public"],
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/errors:go_default_library",
"//staging/src/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",

View File

@ -22,7 +22,6 @@ import (
"sync"
"time"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
"k8s.io/api/admissionregistration/v1beta1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
"k8s.io/apimachinery/pkg/runtime/schema"
@ -32,7 +31,7 @@ import (
"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"
webhookrequest "k8s.io/apiserver/pkg/admission/plugin/webhook/request"
"k8s.io/apiserver/pkg/admission/plugin/webhook/util"
webhookutil "k8s.io/apiserver/pkg/util/webhook"
"k8s.io/klog"
@ -145,20 +144,16 @@ func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Validati
}
}
// Currently dispatcher only supports `v1beta1` AdmissionReview
// TODO: Make the dispatcher capable of sending multiple AdmissionReview versions
if !util.HasAdmissionReviewVersion(v1beta1.SchemeGroupVersion.Version, invocation.Webhook) {
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("webhook does not accept v1beta1 AdmissionReviewRequest")}
uid, request, response, err := webhookrequest.CreateAdmissionObjects(attr, invocation)
if err != nil {
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
// Make the webhook request
request := request.CreateAdmissionReview(attr, invocation)
client, err := d.cm.HookClient(util.HookClientConfigForWebhook(invocation.Webhook))
if err != nil {
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
response := &admissionv1beta1.AdmissionReview{}
r := client.Post().Context(ctx).Body(&request)
r := client.Post().Context(ctx).Body(request)
if h.TimeoutSeconds != nil {
r = r.Timeout(time.Duration(*h.TimeoutSeconds) * time.Second)
}
@ -166,17 +161,19 @@ func (d *validatingDispatcher) callHook(ctx context.Context, h *v1beta1.Validati
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
if response.Response == nil {
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: fmt.Errorf("Webhook response was absent")}
result, err := webhookrequest.VerifyAdmissionResponse(uid, false, response)
if err != nil {
return &webhookutil.ErrCallingWebhook{WebhookName: h.Name, Reason: err}
}
for k, v := range response.Response.AuditAnnotations {
for k, v := range result.AuditAnnotations {
key := h.Name + "/" + k
if err := attr.Attributes.AddAnnotation(key, v); err != nil {
klog.Warningf("Failed to set admission audit annotation %s to %s for validating webhook %s: %v", key, v, h.Name, err)
}
}
if response.Response.Allowed {
if result.Allowed {
return nil
}
return webhookerrors.ToStatusErr(h.Name, response.Response.Result)
return webhookerrors.ToStatusErr(h.Name, result.Result)
}

View File

@ -62,19 +62,21 @@ type ClientManager struct {
}
// NewClientManager creates a clientManager.
func NewClientManager(gv schema.GroupVersion, addToSchemaFunc func(s *runtime.Scheme) error) (ClientManager, error) {
func NewClientManager(gvs []schema.GroupVersion, addToSchemaFuncs ...func(s *runtime.Scheme) error) (ClientManager, error) {
cache, err := lru.New(defaultCacheSize)
if err != nil {
return ClientManager{}, err
}
hookScheme := runtime.NewScheme()
if err := addToSchemaFunc(hookScheme); err != nil {
return ClientManager{}, err
for _, addToSchemaFunc := range addToSchemaFuncs {
if err := addToSchemaFunc(hookScheme); err != nil {
return ClientManager{}, err
}
}
return ClientManager{
cache: cache,
negotiatedSerializer: serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{
Serializer: serializer.NewCodecFactory(hookScheme).LegacyCodec(gv),
Serializer: serializer.NewCodecFactory(hookScheme).LegacyCodec(gvs...),
}),
}, nil
}

View File

@ -14,6 +14,7 @@ go_library(
"//staging/src/k8s.io/api/auditregistration/v1alpha1:go_default_library",
"//staging/src/k8s.io/api/core/v1: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/types:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/audit:go_default_library",
"//staging/src/k8s.io/apiserver/pkg/apis/audit/install:go_default_library",

View File

@ -28,6 +28,7 @@ import (
auditregv1alpha1 "k8s.io/api/auditregistration/v1alpha1"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apimachinery/pkg/types"
auditinternal "k8s.io/apiserver/pkg/apis/audit"
auditinstall "k8s.io/apiserver/pkg/apis/audit/install"
@ -101,7 +102,7 @@ func NewBackend(c *Config) (audit.Backend, error) {
if c.BufferedConfig == nil {
c.BufferedConfig = NewDefaultWebhookBatchConfig()
}
cm, err := webhook.NewClientManager(auditv1.SchemeGroupVersion, func(s *runtime.Scheme) error {
cm, err := webhook.NewClientManager([]schema.GroupVersion{auditv1.SchemeGroupVersion}, func(s *runtime.Scheme) error {
auditinstall.Install(s)
return nil
})

View File

@ -10,6 +10,7 @@ go_library(
importpath = "k8s.io/kubectl/pkg/scheme",
visibility = ["//visibility:public"],
deps = [
"//staging/src/k8s.io/api/admission/v1:go_default_library",
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",

View File

@ -17,7 +17,8 @@ limitations under the License.
package scheme
import (
admissionv1alpha1 "k8s.io/api/admission/v1beta1"
admissionv1 "k8s.io/api/admission/v1"
admissionv1beta1 "k8s.io/api/admission/v1beta1"
admissionregistrationv1 "k8s.io/api/admissionregistration/v1"
admissionregistrationv1beta1 "k8s.io/api/admissionregistration/v1beta1"
appsv1 "k8s.io/api/apps/v1"
@ -63,7 +64,7 @@ func init() {
utilruntime.Must(scheme.AddToScheme(Scheme))
utilruntime.Must(Scheme.SetVersionPriority(corev1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(admissionv1alpha1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(admissionv1beta1.SchemeGroupVersion, admissionv1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(admissionregistrationv1beta1.SchemeGroupVersion, admissionregistrationv1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(appsv1beta1.SchemeGroupVersion, appsv1beta2.SchemeGroupVersion, appsv1.SchemeGroupVersion))
utilruntime.Must(Scheme.SetVersionPriority(authenticationv1.SchemeGroupVersion, authenticationv1beta1.SchemeGroupVersion))

View File

@ -15,7 +15,9 @@ go_test(
],
deps = [
"//cmd/kube-apiserver/app/testing:go_default_library",
"//staging/src/k8s.io/api/admission/v1:go_default_library",
"//staging/src/k8s.io/api/admission/v1beta1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1:go_default_library",
"//staging/src/k8s.io/api/admissionregistration/v1beta1:go_default_library",
"//staging/src/k8s.io/api/apps/v1:go_default_library",
"//staging/src/k8s.io/api/apps/v1beta1:go_default_library",

View File

@ -30,7 +30,9 @@ import (
"testing"
"time"
admissionreviewv1 "k8s.io/api/admission/v1"
"k8s.io/api/admission/v1beta1"
admissionv1 "k8s.io/api/admissionregistration/v1"
admissionv1beta1 "k8s.io/api/admissionregistration/v1beta1"
appsv1beta1 "k8s.io/api/apps/v1beta1"
corev1 "k8s.io/api/core/v1"
@ -151,6 +153,8 @@ var (
)
type webhookOptions struct {
version string
// phase indicates whether this is a mutating or validating webhook
phase string
// converted indicates if this webhook makes use of matchPolicy:equivalent and expects conversion.
@ -165,7 +169,7 @@ type holder struct {
t *testing.T
recordGVR metav1.GroupVersionResource
recordOperation v1beta1.Operation
recordOperation string
recordNamespace string
recordName string
@ -182,7 +186,7 @@ type holder struct {
// When a converted request is recorded, gvrToConvertedGVR[expectGVK] is compared to the GVK seen by the webhook.
gvrToConvertedGVK map[metav1.GroupVersionResource]schema.GroupVersionKind
recorded map[webhookOptions]*v1beta1.AdmissionRequest
recorded map[webhookOptions]*admissionRequest
}
func (h *holder) reset(t *testing.T) {
@ -200,10 +204,12 @@ func (h *holder) reset(t *testing.T) {
h.expectOptions = false
// Set up the recorded map with nil records for all combinations
h.recorded = map[webhookOptions]*v1beta1.AdmissionRequest{}
h.recorded = map[webhookOptions]*admissionRequest{}
for _, phase := range []string{mutation, validation} {
for _, converted := range []bool{true, false} {
h.recorded[webhookOptions{phase: phase, converted: converted}] = nil
for _, version := range []string{"v1", "v1beta1"} {
h.recorded[webhookOptions{version: version, phase: phase, converted: converted}] = nil
}
}
}
}
@ -217,7 +223,7 @@ func (h *holder) expect(gvr schema.GroupVersionResource, gvk, optionsGVK schema.
defer h.lock.Unlock()
h.recordGVR = metav1.GroupVersionResource{Group: gvr.Group, Version: gvr.Version, Resource: gvr.Resource}
h.expectGVK = gvk
h.recordOperation = operation
h.recordOperation = string(operation)
h.recordName = name
h.recordNamespace = namespace
h.expectObject = object
@ -226,14 +232,28 @@ func (h *holder) expect(gvr schema.GroupVersionResource, gvk, optionsGVK schema.
h.expectOptions = options
// Set up the recorded map with nil records for all combinations
h.recorded = map[webhookOptions]*v1beta1.AdmissionRequest{}
h.recorded = map[webhookOptions]*admissionRequest{}
for _, phase := range []string{mutation, validation} {
for _, converted := range []bool{true, false} {
h.recorded[webhookOptions{phase: phase, converted: converted}] = nil
for _, version := range []string{"v1", "v1beta1"} {
h.recorded[webhookOptions{version: version, phase: phase, converted: converted}] = nil
}
}
}
}
func (h *holder) record(phase string, converted bool, request *v1beta1.AdmissionRequest) {
type admissionRequest struct {
Operation string
Resource metav1.GroupVersionResource
SubResource string
Namespace string
Name string
Object runtime.RawExtension
OldObject runtime.RawExtension
Options runtime.RawExtension
}
func (h *holder) record(version string, phase string, converted bool, request *admissionRequest) {
h.lock.Lock()
defer h.lock.Unlock()
@ -286,9 +306,9 @@ func (h *holder) record(phase string, converted bool, request *v1beta1.Admission
}
if debug {
h.t.Logf("recording: %#v = %s %#v %v", webhookOptions{phase: phase, converted: converted}, request.Operation, request.Resource, request.SubResource)
h.t.Logf("recording: %#v = %s %#v %v", webhookOptions{version: version, phase: phase, converted: converted}, request.Operation, request.Resource, request.SubResource)
}
h.recorded[webhookOptions{phase: phase, converted: converted}] = request
h.recorded[webhookOptions{version: version, phase: phase, converted: converted}] = request
}
func (h *holder) verify(t *testing.T) {
@ -297,12 +317,12 @@ func (h *holder) verify(t *testing.T) {
for options, value := range h.recorded {
if err := h.verifyRequest(options.converted, value); err != nil {
t.Errorf("phase:%v, converted:%v error: %v", options.phase, options.converted, err)
t.Errorf("version: %v, phase:%v, converted:%v error: %v", options.version, options.phase, options.converted, err)
}
}
}
func (h *holder) verifyRequest(converted bool, request *v1beta1.AdmissionRequest) error {
func (h *holder) verifyRequest(converted bool, request *admissionRequest) error {
// Check if current resource should be exempted from Admission processing
if admissionExemptResources[gvr(h.recordGVR.Group, h.recordGVR.Version, h.recordGVR.Resource)] {
if request == nil {
@ -366,8 +386,8 @@ func (h *holder) verifyOptions(options runtime.Object) error {
return nil
}
// TestWebhookV1beta1 tests communication between API server and webhook process.
func TestWebhookV1beta1(t *testing.T) {
// TestWebhookAdmission tests communication between API server and webhook process.
func TestWebhookAdmission(t *testing.T) {
// holder communicates expectations to webhooks, and results from webhooks
holder := &holder{
t: t,
@ -386,10 +406,17 @@ func TestWebhookV1beta1(t *testing.T) {
}
webhookMux := http.NewServeMux()
webhookMux.Handle("/"+mutation, newWebhookHandler(t, holder, mutation, false))
webhookMux.Handle("/convert/"+mutation, newWebhookHandler(t, holder, mutation, true))
webhookMux.Handle("/"+validation, newWebhookHandler(t, holder, validation, false))
webhookMux.Handle("/convert/"+validation, newWebhookHandler(t, holder, validation, true))
webhookMux.Handle("/v1beta1/"+mutation, newV1beta1WebhookHandler(t, holder, mutation, false))
webhookMux.Handle("/v1beta1/convert/"+mutation, newV1beta1WebhookHandler(t, holder, mutation, true))
webhookMux.Handle("/v1beta1/"+validation, newV1beta1WebhookHandler(t, holder, validation, false))
webhookMux.Handle("/v1beta1/convert/"+validation, newV1beta1WebhookHandler(t, holder, validation, true))
webhookMux.Handle("/v1/"+mutation, newV1WebhookHandler(t, holder, mutation, false))
webhookMux.Handle("/v1/convert/"+mutation, newV1WebhookHandler(t, holder, mutation, true))
webhookMux.Handle("/v1/"+validation, newV1WebhookHandler(t, holder, validation, false))
webhookMux.Handle("/v1/convert/"+validation, newV1WebhookHandler(t, holder, validation, true))
webhookMux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
holder.t.Errorf("unexpected request to %v", req.URL.Path)
}))
webhookServer := httptest.NewUnstartedServer(webhookMux)
webhookServer.TLS = &tls.Config{
RootCAs: roots,
@ -488,7 +515,8 @@ func TestWebhookV1beta1(t *testing.T) {
// Note: this only works because there are no overlapping resource names in-process that are not co-located
convertedResources := map[string]schema.GroupVersionResource{}
// build the webhook rules enumerating the specific group/version/resources we want
convertedRules := []admissionv1beta1.RuleWithOperations{}
convertedV1beta1Rules := []admissionv1beta1.RuleWithOperations{}
convertedV1Rules := []admissionv1.RuleWithOperations{}
for _, gvr := range gvrsToTest {
metaGVR := metav1.GroupVersionResource{Group: gvr.Group, Version: gvr.Version, Resource: gvr.Resource}
@ -499,10 +527,14 @@ func TestWebhookV1beta1(t *testing.T) {
convertedGVR = gvr
convertedResources[gvr.Resource] = gvr
// add an admission rule indicating we can receive this version
convertedRules = append(convertedRules, admissionv1beta1.RuleWithOperations{
convertedV1beta1Rules = append(convertedV1beta1Rules, admissionv1beta1.RuleWithOperations{
Operations: []admissionv1beta1.OperationType{admissionv1beta1.OperationAll},
Rule: admissionv1beta1.Rule{APIGroups: []string{gvr.Group}, APIVersions: []string{gvr.Version}, Resources: []string{gvr.Resource}},
})
convertedV1Rules = append(convertedV1Rules, admissionv1.RuleWithOperations{
Operations: []admissionv1.OperationType{admissionv1.OperationAll},
Rule: admissionv1.Rule{APIGroups: []string{gvr.Group}, APIVersions: []string{gvr.Version}, Resources: []string{gvr.Resource}},
})
}
// record the expected resource and kind
@ -510,10 +542,16 @@ func TestWebhookV1beta1(t *testing.T) {
holder.gvrToConvertedGVK[metaGVR] = schema.GroupVersionKind{Group: resourcesByGVR[convertedGVR].Group, Version: resourcesByGVR[convertedGVR].Version, Kind: resourcesByGVR[convertedGVR].Kind}
}
if err := createV1beta1MutationWebhook(client, webhookServer.URL+"/"+mutation, webhookServer.URL+"/convert/"+mutation, convertedRules); err != nil {
if err := createV1beta1MutationWebhook(client, webhookServer.URL+"/v1beta1/"+mutation, webhookServer.URL+"/v1beta1/convert/"+mutation, convertedV1beta1Rules); err != nil {
t.Fatal(err)
}
if err := createV1beta1ValidationWebhook(client, webhookServer.URL+"/"+validation, webhookServer.URL+"/convert/"+validation, convertedRules); err != nil {
if err := createV1beta1ValidationWebhook(client, webhookServer.URL+"/v1beta1/"+validation, webhookServer.URL+"/v1beta1/convert/"+validation, convertedV1beta1Rules); err != nil {
t.Fatal(err)
}
if err := createV1MutationWebhook(client, webhookServer.URL+"/v1/"+mutation, webhookServer.URL+"/v1/convert/"+mutation, convertedV1Rules); err != nil {
t.Fatal(err)
}
if err := createV1ValidationWebhook(client, webhookServer.URL+"/v1/"+validation, webhookServer.URL+"/v1/convert/"+validation, convertedV1Rules); err != nil {
t.Fatal(err)
}
@ -1118,7 +1156,7 @@ func testNoPruningCustomFancy(c *testContext) {
// utility methods
//
func newWebhookHandler(t *testing.T, holder *holder, phase string, converted bool) http.Handler {
func newV1beta1WebhookHandler(t *testing.T, holder *holder, phase string, converted bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
data, err := ioutil.ReadAll(r.Body)
@ -1176,18 +1214,124 @@ func newWebhookHandler(t *testing.T, holder *holder, phase string, converted boo
if review.Request.UserInfo.Username == testClientUsername {
// only record requests originating from this integration test's client
holder.record(phase, converted, review.Request)
reviewRequest := &admissionRequest{
Operation: string(review.Request.Operation),
Resource: review.Request.Resource,
SubResource: review.Request.SubResource,
Namespace: review.Request.Namespace,
Name: review.Request.Name,
Object: review.Request.Object,
OldObject: review.Request.OldObject,
Options: review.Request.Options,
}
holder.record("v1beta1", phase, converted, reviewRequest)
}
review.Response = &v1beta1.AdmissionResponse{
Allowed: true,
Result: &metav1.Status{Message: "admitted"},
}
// v1beta1 webhook handler tolerated these not being set. verify the server continues to accept these as unset.
review.APIVersion = ""
review.Kind = ""
review.Response.UID = ""
// If we're mutating, and have an object, return a patch to exercise conversion
if phase == mutation && len(review.Request.Object.Raw) > 0 {
review.Response.Patch = []byte(`[{"op":"add","path":"/foo","value":"test"}]`)
jsonPatch := v1beta1.PatchTypeJSONPatch
review.Response.PatchType = &jsonPatch
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(review); err != nil {
t.Errorf("Marshal of response failed with error: %v", err)
}
})
}
func newV1WebhookHandler(t *testing.T, holder *holder, phase string, converted bool) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
defer r.Body.Close()
data, err := ioutil.ReadAll(r.Body)
if err != nil {
t.Error(err)
return
}
if contentType := r.Header.Get("Content-Type"); contentType != "application/json" {
t.Errorf("contentType=%s, expect application/json", contentType)
return
}
review := admissionreviewv1.AdmissionReview{}
if err := json.Unmarshal(data, &review); err != nil {
t.Errorf("Fail to deserialize object: %s with error: %v", string(data), err)
http.Error(w, err.Error(), 400)
return
}
if review.GetObjectKind().GroupVersionKind() != gvk("admission.k8s.io", "v1", "AdmissionReview") {
err := fmt.Errorf("Invalid admission review kind: %#v", review.GetObjectKind().GroupVersionKind())
t.Error(err)
http.Error(w, err.Error(), 400)
return
}
if len(review.Request.Object.Raw) > 0 {
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := json.Unmarshal(review.Request.Object.Raw, u); err != nil {
t.Errorf("Fail to deserialize object: %s with error: %v", string(review.Request.Object.Raw), err)
http.Error(w, err.Error(), 400)
return
}
review.Request.Object.Object = u
}
if len(review.Request.OldObject.Raw) > 0 {
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := json.Unmarshal(review.Request.OldObject.Raw, u); err != nil {
t.Errorf("Fail to deserialize object: %s with error: %v", string(review.Request.OldObject.Raw), err)
http.Error(w, err.Error(), 400)
return
}
review.Request.OldObject.Object = u
}
if len(review.Request.Options.Raw) > 0 {
u := &unstructured.Unstructured{Object: map[string]interface{}{}}
if err := json.Unmarshal(review.Request.Options.Raw, u); err != nil {
t.Errorf("Fail to deserialize options object: %s for admission request %#+v with error: %v", string(review.Request.Options.Raw), review.Request, err)
http.Error(w, err.Error(), 400)
return
}
review.Request.Options.Object = u
}
if review.Request.UserInfo.Username == testClientUsername {
// only record requests originating from this integration test's client
reviewRequest := &admissionRequest{
Operation: string(review.Request.Operation),
Resource: review.Request.Resource,
SubResource: review.Request.SubResource,
Namespace: review.Request.Namespace,
Name: review.Request.Name,
Object: review.Request.Object,
OldObject: review.Request.OldObject,
Options: review.Request.Options,
}
holder.record("v1", phase, converted, reviewRequest)
}
review.Response = &admissionreviewv1.AdmissionResponse{
Allowed: true,
UID: review.Request.UID,
Result: &metav1.Status{Message: "admitted"},
}
// If we're mutating, and have an object, return a patch to exercise conversion
if phase == mutation && len(review.Request.Object.Raw) > 0 {
review.Response.Patch = []byte(`[{"op":"add","path":"/foo","value":"test"}]`)
jsonPatch := v1beta1.PatchTypeJSONPatch
review.Response.Patch = []byte(`[{"op":"add","path":"/bar","value":"test"}]`)
jsonPatch := admissionreviewv1.PatchTypeJSONPatch
review.Response.PatchType = &jsonPatch
}
@ -1358,6 +1502,84 @@ func createV1beta1MutationWebhook(client clientset.Interface, endpoint, converte
return err
}
func createV1ValidationWebhook(client clientset.Interface, endpoint, convertedEndpoint string, convertedRules []admissionv1.RuleWithOperations) error {
fail := admissionv1.Fail
equivalent := admissionv1.Equivalent
none := admissionv1.SideEffectClassNone
// Attaching Admission webhook to API server
_, err := client.AdmissionregistrationV1().ValidatingWebhookConfigurations().Create(&admissionv1.ValidatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "admissionv1.integration.test"},
Webhooks: []admissionv1.ValidatingWebhook{
{
Name: "admissionv1.integration.test",
ClientConfig: admissionv1.WebhookClientConfig{
URL: &endpoint,
CABundle: localhostCert,
},
Rules: []admissionv1.RuleWithOperations{{
Operations: []admissionv1.OperationType{admissionv1.OperationAll},
Rule: admissionv1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
}},
FailurePolicy: &fail,
AdmissionReviewVersions: []string{"v1", "v1beta1"},
SideEffects: &none,
},
{
Name: "admissionv1.integration.testconversion",
ClientConfig: admissionv1.WebhookClientConfig{
URL: &convertedEndpoint,
CABundle: localhostCert,
},
Rules: convertedRules,
FailurePolicy: &fail,
MatchPolicy: &equivalent,
AdmissionReviewVersions: []string{"v1", "v1beta1"},
SideEffects: &none,
},
},
})
return err
}
func createV1MutationWebhook(client clientset.Interface, endpoint, convertedEndpoint string, convertedRules []admissionv1.RuleWithOperations) error {
fail := admissionv1.Fail
equivalent := admissionv1.Equivalent
none := admissionv1.SideEffectClassNone
// Attaching Mutation webhook to API server
_, err := client.AdmissionregistrationV1().MutatingWebhookConfigurations().Create(&admissionv1.MutatingWebhookConfiguration{
ObjectMeta: metav1.ObjectMeta{Name: "mutationv1.integration.test"},
Webhooks: []admissionv1.MutatingWebhook{
{
Name: "mutationv1.integration.test",
ClientConfig: admissionv1.WebhookClientConfig{
URL: &endpoint,
CABundle: localhostCert,
},
Rules: []admissionv1.RuleWithOperations{{
Operations: []admissionv1.OperationType{admissionv1.OperationAll},
Rule: admissionv1.Rule{APIGroups: []string{"*"}, APIVersions: []string{"*"}, Resources: []string{"*/*"}},
}},
FailurePolicy: &fail,
AdmissionReviewVersions: []string{"v1", "v1beta1"},
SideEffects: &none,
},
{
Name: "mutationv1.integration.testconversion",
ClientConfig: admissionv1.WebhookClientConfig{
URL: &convertedEndpoint,
CABundle: localhostCert,
},
Rules: convertedRules,
FailurePolicy: &fail,
MatchPolicy: &equivalent,
AdmissionReviewVersions: []string{"v1", "v1beta1"},
SideEffects: &none,
},
},
})
return err
}
// localhostCert was generated from crypto/tls/generate_cert.go with the following command:
// go run generate_cert.go --rsa-bits 512 --host 127.0.0.1,::1,example.com --ca --start-date "Jan 1 00:00:00 1970" --duration=1000000h
var localhostCert = []byte(`-----BEGIN CERTIFICATE-----

1
vendor/modules.txt vendored
View File

@ -1029,6 +1029,7 @@ gotest.tools/gotestsum
gotest.tools/gotestsum/internal/junitxml
gotest.tools/gotestsum/testjson
# k8s.io/api v0.0.0 => ./staging/src/k8s.io/api
k8s.io/api/admission/v1
k8s.io/api/admission/v1beta1
k8s.io/api/admissionregistration/v1
k8s.io/api/admissionregistration/v1beta1