2019-01-12 04:58:27 +00:00
/ *
Copyright 2017 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 handlers
import (
"context"
"fmt"
"net/http"
"sync"
"time"
"k8s.io/apimachinery/pkg/api/errors"
2020-03-26 21:07:15 +00:00
"k8s.io/apimachinery/pkg/api/meta"
2019-12-12 01:27:03 +00:00
metainternalversionscheme "k8s.io/apimachinery/pkg/apis/meta/internalversion/scheme"
2019-01-12 04:58:27 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/apis/meta/v1/validation"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/apiserver/pkg/admission"
"k8s.io/apiserver/pkg/audit"
"k8s.io/apiserver/pkg/authorization/authorizer"
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
"k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/apiserver/pkg/features"
"k8s.io/apiserver/pkg/registry/rest"
"k8s.io/apiserver/pkg/util/dryrun"
utilfeature "k8s.io/apiserver/pkg/util/feature"
2019-04-07 17:07:55 +00:00
utiltrace "k8s.io/utils/trace"
2019-01-12 04:58:27 +00:00
)
// UpdateResource returns a function that will handle a resource update
2019-08-30 18:33:25 +00:00
func UpdateResource ( r rest . Updater , scope * RequestScope , admit admission . Interface ) http . HandlerFunc {
2019-01-12 04:58:27 +00:00
return func ( w http . ResponseWriter , req * http . Request ) {
// For performance tracking purposes.
2019-12-12 01:27:03 +00:00
trace := utiltrace . New ( "Update" , utiltrace . Field { Key : "url" , Value : req . URL . Path } , utiltrace . Field { Key : "user-agent" , Value : & lazyTruncatedUserAgent { req } } , utiltrace . Field { Key : "client" , Value : & lazyClientIP { req } } )
2019-01-12 04:58:27 +00:00
defer trace . LogIfLong ( 500 * time . Millisecond )
if isDryRun ( req . URL ) && ! utilfeature . DefaultFeatureGate . Enabled ( features . DryRun ) {
2020-03-26 21:07:15 +00:00
scope . err ( errors . NewBadRequest ( "the dryRun feature is disabled" ) , w , req )
2019-01-12 04:58:27 +00:00
return
}
// TODO: we either want to remove timeout or document it (if we document, move timeout out of this function and declare it in api_installer)
timeout := parseTimeout ( req . URL . Query ( ) . Get ( "timeout" ) )
namespace , name , err := scope . Namer . Name ( req )
if err != nil {
scope . err ( err , w , req )
return
}
2019-09-27 21:51:53 +00:00
ctx , cancel := context . WithTimeout ( req . Context ( ) , timeout )
defer cancel ( )
2019-01-12 04:58:27 +00:00
ctx = request . WithNamespace ( ctx , namespace )
2019-08-30 18:33:25 +00:00
outputMediaType , _ , err := negotiation . NegotiateOutputMediaType ( req , scope . Serializer , scope )
2019-04-07 17:07:55 +00:00
if err != nil {
scope . err ( err , w , req )
return
}
2019-03-04 01:22:32 +00:00
body , err := limitedReadBody ( req , scope . MaxRequestBodyBytes )
2019-01-12 04:58:27 +00:00
if err != nil {
scope . err ( err , w , req )
return
}
options := & metav1 . UpdateOptions { }
2019-12-12 01:27:03 +00:00
if err := metainternalversionscheme . ParameterCodec . DecodeParameters ( req . URL . Query ( ) , scope . MetaGroupVersion , options ) ; err != nil {
2019-01-12 04:58:27 +00:00
err = errors . NewBadRequest ( err . Error ( ) )
scope . err ( err , w , req )
return
}
if errs := validation . ValidateUpdateOptions ( options ) ; len ( errs ) > 0 {
err := errors . NewInvalid ( schema . GroupKind { Group : metav1 . GroupName , Kind : "UpdateOptions" } , "" , errs )
scope . err ( err , w , req )
return
}
2019-08-30 18:33:25 +00:00
options . TypeMeta . SetGroupVersionKind ( metav1 . SchemeGroupVersion . WithKind ( "UpdateOptions" ) )
2019-01-12 04:58:27 +00:00
s , err := negotiation . NegotiateInputSerializer ( req , false , scope . Serializer )
if err != nil {
scope . err ( err , w , req )
return
}
defaultGVK := scope . Kind
original := r . New ( )
trace . Step ( "About to convert to expected version" )
decoder := scope . Serializer . DecoderToVersion ( s . Serializer , scope . HubGroupVersion )
obj , gvk , err := decoder . Decode ( body , & defaultGVK , original )
if err != nil {
err = transformDecodeError ( scope . Typer , err , original , gvk , body )
scope . err ( err , w , req )
return
}
if gvk . GroupVersion ( ) != defaultGVK . GroupVersion ( ) {
err = errors . NewBadRequest ( fmt . Sprintf ( "the API version in the data (%s) does not match the expected API version (%s)" , gvk . GroupVersion ( ) , defaultGVK . GroupVersion ( ) ) )
scope . err ( err , w , req )
return
}
trace . Step ( "Conversion done" )
ae := request . AuditEventFrom ( ctx )
audit . LogRequestObject ( ae , obj , scope . Resource , scope . Subresource , scope . Serializer )
admit = admission . WithAudit ( admit , ae )
if err := checkName ( obj , name , namespace , scope . Namer ) ; err != nil {
scope . err ( err , w , req )
return
}
userInfo , _ := request . UserFrom ( ctx )
2019-04-07 17:07:55 +00:00
transformers := [ ] rest . TransformFunc { }
2020-03-26 21:07:15 +00:00
// allows skipping managedFields update if the resulting object is too big
shouldUpdateManagedFields := true
2019-08-30 18:33:25 +00:00
if scope . FieldManager != nil {
transformers = append ( transformers , func ( _ context . Context , newObj , liveObj runtime . Object ) ( runtime . Object , error ) {
2020-03-26 21:07:15 +00:00
if shouldUpdateManagedFields {
2020-06-23 22:12:14 +00:00
return scope . FieldManager . UpdateNoErrors ( liveObj , newObj , managerOrUserAgent ( options . FieldManager , req . UserAgent ( ) ) ) , nil
2019-08-30 18:33:25 +00:00
}
2020-03-26 21:07:15 +00:00
return newObj , nil
2019-08-30 18:33:25 +00:00
} )
}
2020-03-26 21:07:15 +00:00
2019-01-12 04:58:27 +00:00
if mutatingAdmission , ok := admit . ( admission . MutationInterface ) ; ok {
transformers = append ( transformers , func ( ctx context . Context , newObj , oldObj runtime . Object ) ( runtime . Object , error ) {
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 ) {
2019-09-27 21:51:53 +00:00
return newObj , mutatingAdmission . Admit ( ctx , admission . NewAttributesRecord ( newObj , nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Create , updateToCreateOptions ( options ) , dryrun . IsDryRun ( options . DryRun ) , userInfo ) , scope )
2019-01-12 04:58:27 +00:00
}
} else {
if mutatingAdmission . Handles ( admission . Update ) {
2019-09-27 21:51:53 +00:00
return newObj , mutatingAdmission . Admit ( ctx , admission . NewAttributesRecord ( newObj , oldObj , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Update , options , dryrun . IsDryRun ( options . DryRun ) , userInfo ) , scope )
2019-01-12 04:58:27 +00:00
}
}
return newObj , nil
} )
}
createAuthorizerAttributes := authorizer . AttributesRecord {
User : userInfo ,
ResourceRequest : true ,
Path : req . URL . Path ,
Verb : "create" ,
APIGroup : scope . Resource . Group ,
APIVersion : scope . Resource . Version ,
Resource : scope . Resource . Resource ,
Subresource : scope . Subresource ,
Namespace : namespace ,
Name : name ,
}
trace . Step ( "About to store object in database" )
wasCreated := false
2020-03-26 21:07:15 +00:00
requestFunc := func ( ) ( runtime . Object , error ) {
2019-01-12 04:58:27 +00:00
obj , created , err := r . Update (
ctx ,
name ,
rest . DefaultUpdatedObjectInfo ( obj , transformers ... ) ,
withAuthorization ( rest . AdmissionToValidateObjectFunc (
admit ,
2019-08-30 18:33:25 +00:00
admission . NewAttributesRecord ( nil , nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Create , updateToCreateOptions ( options ) , dryrun . IsDryRun ( options . DryRun ) , userInfo ) , scope ) ,
2019-01-12 04:58:27 +00:00
scope . Authorizer , createAuthorizerAttributes ) ,
rest . AdmissionToValidateObjectUpdateFunc (
admit ,
2019-08-30 18:33:25 +00:00
admission . NewAttributesRecord ( nil , nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Update , options , dryrun . IsDryRun ( options . DryRun ) , userInfo ) , scope ) ,
2019-01-12 04:58:27 +00:00
false ,
options ,
)
wasCreated = created
return obj , err
2020-03-26 21:07:15 +00:00
}
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
result , err := requestFunc ( )
// If the object wasn't committed to storage because it's serialized size was too large,
// it is safe to remove managedFields (which can be large) and try again.
if isTooLargeError ( err ) && scope . FieldManager != nil {
if accessor , accessorErr := meta . Accessor ( obj ) ; accessorErr == nil {
accessor . SetManagedFields ( nil )
shouldUpdateManagedFields = false
result , err = requestFunc ( )
}
}
return result , err
2019-01-12 04:58:27 +00:00
} )
if err != nil {
scope . err ( err , w , req )
return
}
trace . Step ( "Object stored in database" )
status := http . StatusOK
if wasCreated {
status = http . StatusCreated
}
2019-08-30 18:33:25 +00:00
transformResponseObject ( ctx , scope , trace , req , w , status , outputMediaType , result )
2019-01-12 04:58:27 +00:00
}
}
func withAuthorization ( validate rest . ValidateObjectFunc , a authorizer . Authorizer , attributes authorizer . Attributes ) rest . ValidateObjectFunc {
var once sync . Once
var authorizerDecision authorizer . Decision
var authorizerReason string
var authorizerErr error
2019-09-27 21:51:53 +00:00
return func ( ctx context . Context , obj runtime . Object ) error {
2019-01-12 04:58:27 +00:00
if a == nil {
return errors . NewInternalError ( fmt . Errorf ( "no authorizer provided, unable to authorize a create on update" ) )
}
once . Do ( func ( ) {
2019-12-12 01:27:03 +00:00
authorizerDecision , authorizerReason , authorizerErr = a . Authorize ( ctx , attributes )
2019-01-12 04:58:27 +00:00
} )
// an authorizer like RBAC could encounter evaluation errors and still allow the request, so authorizer decision is checked before error here.
if authorizerDecision == authorizer . DecisionAllow {
// Continue to validating admission
2019-09-27 21:51:53 +00:00
return validate ( ctx , obj )
2019-01-12 04:58:27 +00:00
}
if authorizerErr != nil {
return errors . NewInternalError ( authorizerErr )
}
// The user is not authorized to perform this action, so we need to build the error response
gr := schema . GroupResource {
Group : attributes . GetAPIGroup ( ) ,
Resource : attributes . GetResource ( ) ,
}
name := attributes . GetName ( )
err := fmt . Errorf ( "%v" , authorizerReason )
return errors . NewForbidden ( gr , name , err )
}
}
2019-08-30 18:33:25 +00:00
// updateToCreateOptions creates a CreateOptions with the same field values as the provided UpdateOptions.
func updateToCreateOptions ( uo * metav1 . UpdateOptions ) * metav1 . CreateOptions {
if uo == nil {
return nil
}
co := & metav1 . CreateOptions {
DryRun : uo . DryRun ,
FieldManager : uo . FieldManager ,
}
co . TypeMeta . SetGroupVersionKind ( metav1 . SchemeGroupVersion . WithKind ( "CreateOptions" ) )
return co
}