mirror of
https://github.com/k3s-io/kubernetes.git
synced 2025-07-23 19:56:01 +00:00
Merge pull request #80231 from liggitt/admissionreview-v1
Promote admissionreview to v1
This commit is contained in:
commit
c981c65c90
10
api/openapi-spec/swagger.json
generated
10
api/openapi-spec/swagger.json
generated
@ -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",
|
||||
|
@ -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"],
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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"],
|
||||
|
@ -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",
|
||||
|
@ -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))
|
||||
}
|
||||
|
@ -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.
|
||||
|
39
pkg/apis/admission/v1/BUILD
Normal file
39
pkg/apis/admission/v1/BUILD
Normal 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"],
|
||||
)
|
24
pkg/apis/admission/v1/doc.go
Normal file
24
pkg/apis/admission/v1/doc.go
Normal 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"
|
46
pkg/apis/admission/v1/register.go
Normal file
46
pkg/apis/admission/v1/register.go
Normal 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)
|
||||
}
|
206
pkg/apis/admission/v1/zz_generated.conversion.go
generated
Normal file
206
pkg/apis/admission/v1/zz_generated.conversion.go
generated
Normal 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)
|
||||
}
|
32
pkg/apis/admission/v1/zz_generated.defaults.go
generated
Normal file
32
pkg/apis/admission/v1/zz_generated.defaults.go
generated
Normal 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
|
||||
}
|
@ -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, ", "))))
|
||||
}
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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",
|
||||
|
39
staging/src/k8s.io/api/admission/v1/BUILD
Normal file
39
staging/src/k8s.io/api/admission/v1/BUILD
Normal 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"],
|
||||
)
|
23
staging/src/k8s.io/api/admission/v1/doc.go
Normal file
23
staging/src/k8s.io/api/admission/v1/doc.go
Normal 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"
|
1769
staging/src/k8s.io/api/admission/v1/generated.pb.go
generated
Normal file
1769
staging/src/k8s.io/api/admission/v1/generated.pb.go
generated
Normal file
File diff suppressed because it is too large
Load Diff
160
staging/src/k8s.io/api/admission/v1/generated.proto
Normal file
160
staging/src/k8s.io/api/admission/v1/generated.proto
Normal 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;
|
||||
}
|
||||
|
51
staging/src/k8s.io/api/admission/v1/register.go
Normal file
51
staging/src/k8s.io/api/admission/v1/register.go
Normal 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
|
||||
}
|
162
staging/src/k8s.io/api/admission/v1/types.go
Normal file
162
staging/src/k8s.io/api/admission/v1/types.go
Normal 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"
|
||||
)
|
77
staging/src/k8s.io/api/admission/v1/types_swagger_doc_generated.go
generated
Normal file
77
staging/src/k8s.io/api/admission/v1/types_swagger_doc_generated.go
generated
Normal 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
|
136
staging/src/k8s.io/api/admission/v1/zz_generated.deepcopy.go
generated
Normal file
136
staging/src/k8s.io/api/admission/v1/zz_generated.deepcopy.go
generated
Normal 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
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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`.",
|
||||
}
|
||||
|
@ -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,
|
||||
|
81
staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.json
vendored
Normal file
81
staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.json
vendored
Normal 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"
|
||||
}
|
||||
}
|
||||
}
|
BIN
staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.pb
vendored
Normal file
BIN
staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.pb
vendored
Normal file
Binary file not shown.
80
staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.yaml
vendored
Normal file
80
staging/src/k8s.io/api/testdata/HEAD/admission.k8s.io.v1.AdmissionReview.yaml
vendored
Normal 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鈶
|
@ -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, ", "))))
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
|
@ -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",
|
||||
],
|
||||
)
|
||||
|
@ -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,
|
||||
|
@ -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))
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
@ -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",
|
||||
|
@ -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",
|
||||
|
@ -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)
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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",
|
||||
|
@ -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
|
||||
})
|
||||
|
@ -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",
|
||||
|
@ -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))
|
||||
|
@ -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",
|
||||
|
@ -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
1
vendor/modules.txt
vendored
@ -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
|
||||
|
Loading…
Reference in New Issue
Block a user