Skip to content

Commit 140c8c7

Browse files
committed
Pass {Operation}Option to Webhooks
1 parent 0252a32 commit 140c8c7

File tree

15 files changed

+186
-30
lines changed

15 files changed

+186
-30
lines changed

pkg/apis/admission/types.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,8 @@ type AdmissionRequest struct {
6363
// Namespace is the namespace associated with the request (if any).
6464
// +optional
6565
Namespace string
66-
// Operation is the operation being performed
66+
// Operation is the operation being performed. This may be different than the operation
67+
// requested. e.g. a patch can result in either a CREATE or UPDATE Operation.
6768
Operation Operation
6869
// UserInfo is information about the requesting user
6970
UserInfo authentication.UserInfo
@@ -78,6 +79,13 @@ type AdmissionRequest struct {
7879
// Defaults to false.
7980
// +optional
8081
DryRun *bool
82+
// Options is the operation option structure of the operation being performed.
83+
// e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be
84+
// different than the options the caller provided. e.g. for a patch request the performed
85+
// Operation might be a CREATE, in which case the Options will a
86+
// `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`.
87+
// +optional
88+
Options runtime.Object
8189
}
8290

8391
// AdmissionResponse describes an admission response.

staging/src/k8s.io/api/admission/v1beta1/types.go

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,8 @@ type AdmissionRequest struct {
6161
// Namespace is the namespace associated with the request (if any).
6262
// +optional
6363
Namespace string `json:"namespace,omitempty" protobuf:"bytes,6,opt,name=namespace"`
64-
// Operation is the operation being performed
64+
// Operation is the operation being performed. This may be different than the operation
65+
// requested. e.g. a patch can result in either a CREATE or UPDATE Operation.
6566
Operation Operation `json:"operation" protobuf:"bytes,7,opt,name=operation"`
6667
// UserInfo is information about the requesting user
6768
UserInfo authenticationv1.UserInfo `json:"userInfo" protobuf:"bytes,8,opt,name=userInfo"`
@@ -75,6 +76,13 @@ type AdmissionRequest struct {
7576
// Defaults to false.
7677
// +optional
7778
DryRun *bool `json:"dryRun,omitempty" protobuf:"varint,11,opt,name=dryRun"`
79+
// Options is the operation option structure of the operation being performed.
80+
// e.g. `meta.k8s.io/v1.DeleteOptions` or `meta.k8s.io/v1.CreateOptions`. This may be
81+
// different than the options the caller provided. e.g. for a patch request the performed
82+
// Operation might be a CREATE, in which case the Options will a
83+
// `meta.k8s.io/v1.CreateOptions` even though the caller provided `meta.k8s.io/v1.PatchOptions`.
84+
// +optional
85+
Options runtime.RawExtension `json:"options,omitempty" protobuf:"bytes,12,opt,name=options"`
7886
}
7987

8088
// AdmissionResponse describes an admission response.

staging/src/k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1/types.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -443,7 +443,7 @@ type ConversionRequest struct {
443443
// ConversionResponse describes a conversion response.
444444
type ConversionResponse struct {
445445
// `uid` is an identifier for the individual request/response.
446-
// This should be copied over from the corresponding AdmissionRequest.
446+
// This should be copied over from the corresponding ConversionRequest.
447447
UID types.UID `json:"uid" protobuf:"bytes,1,name=uid"`
448448
// `convertedObjects` is the list of converted version of `request.objects` if the `result` is successful otherwise empty.
449449
// The webhook is expected to set apiVersion of these objects to the ConversionRequest.desiredAPIVersion. The list

staging/src/k8s.io/apiserver/pkg/admission/attributes.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ type attributesRecord struct {
3434
resource schema.GroupVersionResource
3535
subresource string
3636
operation Operation
37+
options runtime.Object
3738
dryRun bool
3839
object runtime.Object
3940
oldObject runtime.Object
@@ -45,14 +46,15 @@ type attributesRecord struct {
4546
annotationsLock sync.RWMutex
4647
}
4748

48-
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, dryRun bool, userInfo user.Info) Attributes {
49+
func NewAttributesRecord(object runtime.Object, oldObject runtime.Object, kind schema.GroupVersionKind, namespace, name string, resource schema.GroupVersionResource, subresource string, operation Operation, operationOptions runtime.Object, dryRun bool, userInfo user.Info) Attributes {
4950
return &attributesRecord{
5051
kind: kind,
5152
namespace: namespace,
5253
name: name,
5354
resource: resource,
5455
subresource: subresource,
5556
operation: operation,
57+
options: operationOptions,
5658
dryRun: dryRun,
5759
object: object,
5860
oldObject: oldObject,
@@ -84,6 +86,10 @@ func (record *attributesRecord) GetOperation() Operation {
8486
return record.operation
8587
}
8688

89+
func (record *attributesRecord) GetOperationOptions() runtime.Object {
90+
return record.options
91+
}
92+
8793
func (record *attributesRecord) IsDryRun() bool {
8894
return record.dryRun
8995
}

staging/src/k8s.io/apiserver/pkg/admission/interfaces.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ type Attributes interface {
4141
GetSubresource() string
4242
// GetOperation is the operation being performed
4343
GetOperation() Operation
44+
// GetOperationOptions is the options for the operation being performed
45+
GetOperationOptions() runtime.Object
4446
// IsDryRun indicates that modifications will definitely not be persisted for this request. This is to prevent
4547
// admission controllers with side effects and a method of reconciliation from being overwhelmed.
4648
// However, a value of false for this does not mean that the modification will be persisted, because it

staging/src/k8s.io/apiserver/pkg/admission/plugin/webhook/request/admissionreview.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ func CreateAdmissionReview(attr *generic.VersionedAttributes) admissionv1beta1.A
6868
Object: attr.VersionedOldObject,
6969
},
7070
DryRun: &dryRun,
71+
Options: runtime.RawExtension{
72+
Object: attr.GetOperationOptions(),
73+
},
7174
},
7275
}
7376
}

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/BUILD

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ go_test(
2727
"//staging/src/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
2828
"//staging/src/k8s.io/apimachinery/pkg/types:go_default_library",
2929
"//staging/src/k8s.io/apimachinery/pkg/util/diff:go_default_library",
30+
"//staging/src/k8s.io/apimachinery/pkg/util/json:go_default_library",
3031
"//staging/src/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
3132
"//staging/src/k8s.io/apimachinery/pkg/util/strategicpatch:go_default_library",
3233
"//staging/src/k8s.io/apiserver/pkg/admission:go_default_library",

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/create.go

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,7 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
106106
scope.err(err, w, req)
107107
return
108108
}
109+
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
109110

110111
defaultGVK := scope.Kind
111112
original := r.New()
@@ -128,7 +129,7 @@ func createHandler(r rest.NamedCreater, scope *RequestScope, admit admission.Int
128129
audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
129130

130131
userInfo, _ := request.UserFrom(ctx)
131-
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, dryrun.IsDryRun(options.DryRun), userInfo)
132+
admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, options, dryrun.IsDryRun(options.DryRun), userInfo)
132133
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
133134
err = mutatingAdmission.Admit(admissionAttributes, scope)
134135
if err != nil {

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/delete.go

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -113,11 +113,12 @@ func DeleteResource(r rest.GracefulDeleter, allowsOptions bool, scope *RequestSc
113113
scope.err(err, w, req)
114114
return
115115
}
116+
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions"))
116117

117118
trace.Step("About to check admission control")
118119
if admit != nil && admit.Handles(admission.Delete) {
119120
userInfo, _ := request.UserFrom(ctx)
120-
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, dryrun.IsDryRun(options.DryRun), userInfo)
121+
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
121122
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
122123
if err := mutatingAdmission.Admit(attrs, scope); err != nil {
123124
scope.err(err, w, req)
@@ -236,6 +237,8 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
236237
scope.err(err, w, req)
237238
return
238239
}
240+
// For backwards compatibility, we need to allow existing clients to submit per group DeleteOptions
241+
// It is also allowed to pass a body with meta.k8s.io/v1.DeleteOptions
239242
defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions")
240243
obj, _, err := scope.Serializer.DecoderToVersion(s.Serializer, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options)
241244
if err != nil {
@@ -262,11 +265,12 @@ func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope *RequestSc
262265
scope.err(err, w, req)
263266
return
264267
}
268+
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("DeleteOptions"))
265269

266270
admit = admission.WithAudit(admit, ae)
267271
if admit != nil && admit.Handles(admission.Delete) {
268272
userInfo, _ := request.UserFrom(ctx)
269-
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, dryrun.IsDryRun(options.DryRun), userInfo)
273+
attrs := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, options, dryrun.IsDryRun(options.DryRun), userInfo)
270274
if mutatingAdmission, ok := admit.(admission.MutationInterface); ok {
271275
err = mutatingAdmission.Admit(attrs, scope)
272276
if err != nil {

staging/src/k8s.io/apiserver/pkg/endpoints/handlers/patch.go

Lines changed: 35 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import (
2323
"strings"
2424
"time"
2525

26-
"github.com/evanphx/json-patch"
26+
jsonpatch "github.com/evanphx/json-patch"
2727
"k8s.io/apimachinery/pkg/api/errors"
2828
"k8s.io/apimachinery/pkg/api/meta"
2929
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
@@ -118,6 +118,7 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac
118118
scope.err(err, w, req)
119119
return
120120
}
121+
options.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("PatchOptions"))
121122

122123
ae := request.AuditEventFrom(ctx)
123124
admit = admission.WithAudit(admit, ae)
@@ -151,6 +152,7 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac
151152
scope.Resource,
152153
scope.Subresource,
153154
admission.Create,
155+
patchToCreateOptions(options),
154156
dryrun.IsDryRun(options.DryRun),
155157
userInfo)
156158
staticUpdateAttributes := admission.NewAttributesRecord(
@@ -162,6 +164,7 @@ func PatchResource(r rest.Patcher, scope *RequestScope, admit admission.Interfac
162164
scope.Resource,
163165
scope.Subresource,
164166
admission.Update,
167+
patchToUpdateOptions(options),
165168
dryrun.IsDryRun(options.DryRun),
166169
userInfo,
167170
)
@@ -489,9 +492,9 @@ func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object)
489492
return objToUpdate, nil
490493
}
491494

492-
func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation) admission.Attributes {
495+
func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime.Object, currentObject runtime.Object, operation admission.Operation, operationOptions runtime.Object) admission.Attributes {
493496
userInfo, _ := request.UserFrom(ctx)
494-
return admission.NewAttributesRecord(updatedObject, currentObject, p.kind, p.namespace, p.name, p.resource, p.subresource, operation, p.dryRun, userInfo)
497+
return admission.NewAttributesRecord(updatedObject, currentObject, p.kind, p.namespace, p.name, p.resource, p.subresource, operation, operationOptions, p.dryRun, userInfo)
495498
}
496499

497500
// applyAdmission is called every time GuaranteedUpdate asks for the updated object,
@@ -500,16 +503,19 @@ func (p *patcher) admissionAttributes(ctx context.Context, updatedObject runtime
500503
func (p *patcher) applyAdmission(ctx context.Context, patchedObject runtime.Object, currentObject runtime.Object) (runtime.Object, error) {
501504
p.trace.Step("About to check admission control")
502505
var operation admission.Operation
506+
var options runtime.Object
503507
if hasUID, err := hasUID(currentObject); err != nil {
504508
return nil, err
505509
} else if !hasUID {
506510
operation = admission.Create
507511
currentObject = nil
512+
options = patchToCreateOptions(p.options)
508513
} else {
509514
operation = admission.Update
515+
options = patchToUpdateOptions(p.options)
510516
}
511517
if p.admissionCheck != nil && p.admissionCheck.Handles(operation) {
512-
attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation)
518+
attributes := p.admissionAttributes(ctx, patchedObject, currentObject, operation, options)
513519
return patchedObject, p.admissionCheck.Admit(attributes, p.objectInterfaces)
514520
}
515521
return patchedObject, nil
@@ -551,11 +557,8 @@ func (p *patcher) patchResource(ctx context.Context, scope *RequestScope) (runti
551557
wasCreated := false
552558
p.updatedObjectInfo = rest.DefaultUpdatedObjectInfo(nil, p.applyPatch, p.applyAdmission)
553559
result, err := finishRequest(p.timeout, func() (runtime.Object, error) {
554-
// TODO: Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate
555-
options, err := patchToUpdateOptions(p.options)
556-
if err != nil {
557-
return nil, err
558-
}
560+
// Pass in UpdateOptions to override UpdateStrategy.AllowUpdateOnCreate
561+
options := patchToUpdateOptions(p.options)
559562
updateObject, created, updateErr := p.restPatcher.Update(ctx, p.name, p.updatedObjectInfo, p.createValidation, p.updateValidation, p.forceAllowCreate, options)
560563
wasCreated = created
561564
return updateObject, updateErr
@@ -600,12 +603,28 @@ func interpretStrategicMergePatchError(err error) error {
600603
}
601604
}
602605

603-
func patchToUpdateOptions(po *metav1.PatchOptions) (*metav1.UpdateOptions, error) {
604-
b, err := json.Marshal(po)
605-
if err != nil {
606-
return nil, err
606+
// patchToUpdateOptions creates an UpdateOptions with the same field values as the provided PatchOptions.
607+
func patchToUpdateOptions(po *metav1.PatchOptions) *metav1.UpdateOptions {
608+
if po == nil {
609+
return nil
610+
}
611+
uo := &metav1.UpdateOptions{
612+
DryRun: po.DryRun,
613+
FieldManager: po.FieldManager,
614+
}
615+
uo.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("UpdateOptions"))
616+
return uo
617+
}
618+
619+
// patchToCreateOptions creates an CreateOptions with the same field values as the provided PatchOptions.
620+
func patchToCreateOptions(po *metav1.PatchOptions) *metav1.CreateOptions {
621+
if po == nil {
622+
return nil
623+
}
624+
co := &metav1.CreateOptions{
625+
DryRun: po.DryRun,
626+
FieldManager: po.FieldManager,
607627
}
608-
uo := metav1.UpdateOptions{}
609-
err = json.Unmarshal(b, &uo)
610-
return &uo, err
628+
co.TypeMeta.SetGroupVersionKind(metav1.SchemeGroupVersion.WithKind("CreateOptions"))
629+
return co
611630
}

0 commit comments

Comments
 (0)