Change:
Is this a BUG REPORT or FEATURE REQUEST?: /kind bug /sig api-machinery What happened: The that we use to make a create validation function is given admission.Update as its operation field. In the create validation we then use the provided operation (admission.Update) , when we call the admission controllers, even though a new object is being created. A similar thing is happening with the mutating admission. This means that when an objects is created on update, the admission controllers will see this as an update request. Meaning there could be an objects in storage which never passed create admission validation and were never modified by create admission mutation. I think this is a bug. What you expected to happen: Admission controllers would handle a create-on-update exactly the same as a normal create
I think the solution to this would be to separate the static admission attributes into one for the create validation and one for the update validation. Also, the function which performs the mutating admission needs to determine whether or not it is an actual update or if the old object doesn't exist (probably by checking uid) and then call the mutating admission correctly based on which operation is being performed.
/assign I might be interested to give it a try.
How to reproduce this bug?
Instead of a reproduceable BUG, I think it's like a mismatch of conceptual design and actual implementation. If a resource allows create-on-update in its strategy e.g.: Then admission plugins would receive operations (CREATE/UPDATE) on UPDATE requests. Some problems might happen the admission plugin is written without considering the circumstances that admitting a UPDATE operation might result a object creation into the storage. Risky and confusing, WDYT?
With the current build in admission controllers, I'm not sure if there are any bugs. Maybe something with resource quota? The real problem is that there is no way to know which webhook authors will have a dependency on seeing a "create" operation for every object put into storage. If a webhook relies on creates to implement its custom logic (which should be allowed), then objects created with an update would currently be missed by the webhook.

Discussion:
if evaluator == nil { listerFunc := generic.ListerFuncForResourceFunc(qm.informerFactory.ForResource) listResourceFunc := generic.ListResourceUsingListerFunc(listerFunc, resource) <del> evaluator = generic.NewObjectCountEvaluator(false, resource.GroupResource(), listResourceFunc, "") </del> <ins> evaluator = generic.NewObjectCountEvaluator(resource.GroupResource(), listResourceFunc, "") </ins> qm.registry.Add(evaluator) glog.Infof("QuotaMonitor created object count evaluator for %s", resource.GroupResource()) }

// these evaluators require an alias for backwards compatibility for gvr, alias := range legacyObjectCountAliases { result = append(result, <del> generic.NewObjectCountEvaluator(false, gvr.GroupResource(), generic.ListResourceUsingListerFunc(f, gvr), alias)) </del> <ins> generic.NewObjectCountEvaluator(gvr.GroupResource(), generic.ListResourceUsingListerFunc(f, gvr), alias)) </ins> } return result }

// that associates usage of the specified resource based on the number of items // returned by the specified listing function. type objectCountEvaluator struct { <del> // allowCreateOnUpdate if true will ensure the evaluator tracks create // and update operations. allowCreateOnUpdate bool </del> // GroupResource that this evaluator tracks. // It is used to construct a generic object count quota name groupResource schema.GroupResource

// Handles returns true if the object count evaluator needs to track this attributes. func (o *objectCountEvaluator) Handles(a admission.Attributes) bool { operation := a.GetOperation() <del> return operation == admission.Create || (o.allowCreateOnUpdate && operation == admission.Update) </del> <ins> return operation == admission.Create </ins> } // Matches returns true if the evaluator matches the specified quota with the provided input item

// purposes for the legacy object counting names in quota.  Unless its supporting // backward compatibility, alias should not be used. func NewObjectCountEvaluator( <del> allowCreateOnUpdate bool, </del> groupResource schema.GroupResource, listFuncByNamespace ListFuncByNamespace, alias api.ResourceName) quota.Evaluator {

} return &objectCountEvaluator{ <del> allowCreateOnUpdate: allowCreateOnUpdate, </del> groupResource:       groupResource, listFuncByNamespace: listFuncByNamespace, resourceNames:       resourceNames,

if evaluator == nil { // create an object count evaluator if no evaluator previously registered // note, we do not need aggregate usage here, so we pass a nil informer func <del> evaluator = generic.NewObjectCountEvaluator(false, gr, nil, "") </del> <ins> evaluator = generic.NewObjectCountEvaluator(gr, nil, "") </ins> e.registry.Add(evaluator) glog.Infof("quota admission added evaluator for: %s", gr) }

userInfo, _ := request.UserFrom(ctx) staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo) admissionCheck := func(updatedObject runtime.Object, currentObject runtime.Object) error { <ins> // if we allow create-on-patch, we have this TODO: call the mutating admission chain with the CREATE verb instead of UPDATE </ins> if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && admit.Handles(admission.Update) { return mutatingAdmission.Admit(admission.NewAttributesRecord(updatedObject, currentObject, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) }

} userInfo, _ := request.UserFrom(ctx) <del> staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo) </del> var transformers []rest.TransformFunc <del> if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Update) { </del> <ins> if mutatingAdmission, ok := admit.(admission.MutationInterface); ok { </ins> transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) { <del> return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) </del> <ins> isNotZeroObject, err := hasUID(oldObj) if err != nil { return nil, fmt.Errorf("unexpected error when extracting UID from oldObj: %v", err.Error()) } else if !isNotZeroObject { if mutatingAdmission.Handles(admission.Create) { return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)) } } else { if mutatingAdmission.Handles(admission.Update) { return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) } } return newObj, nil </ins> }) } createAuthorizerAttributes := authorizer.AttributesRecord{

ctx, name, rest.DefaultUpdatedObjectInfo(obj, transformers...), <del> withAuthorization(rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes), scope.Authorizer, createAuthorizerAttributes), rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes), </del> <ins> withAuthorization(rest.AdmissionToValidateObjectFunc( admit, admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)), scope.Authorizer, createAuthorizerAttributes), rest.AdmissionToValidateObjectUpdateFunc( admit, admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)), </ins> false, ) wasCreated = created