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 (
2019-04-07 17:07:55 +00:00
"bytes"
2019-01-12 04:58:27 +00:00
"context"
"fmt"
"net/http"
2019-04-07 17:07:55 +00:00
"strings"
2019-01-12 04:58:27 +00:00
"time"
2019-04-07 17:07:55 +00:00
"unicode"
"unicode/utf8"
2019-01-12 04:58:27 +00:00
"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/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
)
2019-08-30 18:33:25 +00:00
func createHandler ( r rest . NamedCreater , scope * RequestScope , admit admission . Interface , includeName bool ) 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 ( "Create" , 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" ) )
2019-09-27 21:51:53 +00:00
namespace , name , err := scope . Namer . Name ( req )
2019-01-12 04:58:27 +00:00
if err != nil {
2019-09-27 21:51:53 +00:00
if includeName {
// name was required, return
scope . err ( err , w , req )
return
}
// otherwise attempt to look up the namespace
namespace , err = scope . Namer . Namespace ( req )
if err != nil {
scope . err ( err , w , req )
return
}
2019-01-12 04:58:27 +00:00
}
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-01-12 04:58:27 +00:00
gv := scope . Kind . GroupVersion ( )
s , err := negotiation . NegotiateInputSerializer ( req , false , scope . Serializer )
if err != nil {
scope . err ( err , w , req )
return
}
2019-04-07 17:07:55 +00:00
2019-01-12 04:58:27 +00:00
decoder := scope . Serializer . DecoderToVersion ( s . Serializer , scope . HubGroupVersion )
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 . CreateOptions { }
values := req . URL . Query ( )
2019-12-12 01:27:03 +00:00
if err := metainternalversionscheme . ParameterCodec . DecodeParameters ( values , 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 . ValidateCreateOptions ( options ) ; len ( errs ) > 0 {
err := errors . NewInvalid ( schema . GroupKind { Group : metav1 . GroupName , Kind : "CreateOptions" } , "" , errs )
scope . err ( err , w , req )
return
}
2019-08-30 18:33:25 +00:00
options . TypeMeta . SetGroupVersionKind ( metav1 . SchemeGroupVersion . WithKind ( "CreateOptions" ) )
2019-01-12 04:58:27 +00:00
defaultGVK := scope . Kind
original := r . New ( )
trace . Step ( "About to convert to expected version" )
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 ( ) != gv {
err = errors . NewBadRequest ( fmt . Sprintf ( "the API version in the data (%s) does not match the expected API version (%v)" , gvk . GroupVersion ( ) . String ( ) , gv . String ( ) ) )
scope . err ( err , w , req )
return
}
trace . Step ( "Conversion done" )
ae := request . AuditEventFrom ( ctx )
admit = admission . WithAudit ( admit , ae )
audit . LogRequestObject ( ae , obj , scope . Resource , scope . Subresource , scope . Serializer )
userInfo , _ := request . UserFrom ( ctx )
2019-09-27 21:51:53 +00:00
// On create, get name from new object if unset
if len ( name ) == 0 {
_ , name , _ = scope . Namer . ObjectName ( obj )
}
2019-08-30 18:33:25 +00:00
2019-01-12 04:58:27 +00:00
trace . Step ( "About to store object in database" )
2020-03-26 21:07:15 +00:00
admissionAttributes := admission . NewAttributesRecord ( obj , nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Create , options , dryrun . IsDryRun ( options . DryRun ) , userInfo )
requestFunc := func ( ) ( runtime . Object , error ) {
2019-01-12 04:58:27 +00:00
return r . Create (
ctx ,
name ,
obj ,
2019-08-30 18:33:25 +00:00
rest . AdmissionToValidateObjectFunc ( admit , admissionAttributes , scope ) ,
2019-01-12 04:58:27 +00:00
options ,
)
2020-03-26 21:07:15 +00:00
}
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
if scope . FieldManager != nil {
liveObj , err := scope . Creater . New ( scope . Kind )
if err != nil {
return nil , fmt . Errorf ( "failed to create new object (Create for %v): %v" , scope . Kind , err )
}
obj , err = scope . FieldManager . Update ( liveObj , obj , managerOrUserAgent ( options . FieldManager , req . UserAgent ( ) ) )
if err != nil {
return nil , fmt . Errorf ( "failed to update object (Create for %v) managed fields: %v" , scope . Kind , err )
}
}
if mutatingAdmission , ok := admit . ( admission . MutationInterface ) ; ok && mutatingAdmission . Handles ( admission . Create ) {
if err := mutatingAdmission . Admit ( ctx , admissionAttributes , scope ) ; err != nil {
return nil , err
}
}
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 ) {
if accessor , accessorErr := meta . Accessor ( obj ) ; accessorErr == nil {
accessor . SetManagedFields ( nil )
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" )
code := http . StatusCreated
status , ok := result . ( * metav1 . Status )
if ok && err == nil && status . Code == 0 {
status . Code = int32 ( code )
}
2019-08-30 18:33:25 +00:00
transformResponseObject ( ctx , scope , trace , req , w , code , outputMediaType , result )
2019-01-12 04:58:27 +00:00
}
}
// CreateNamedResource returns a function that will handle a resource creation with name.
2019-08-30 18:33:25 +00:00
func CreateNamedResource ( r rest . NamedCreater , scope * RequestScope , admission admission . Interface ) http . HandlerFunc {
2019-01-12 04:58:27 +00:00
return createHandler ( r , scope , admission , true )
}
// CreateResource returns a function that will handle a resource creation.
2019-08-30 18:33:25 +00:00
func CreateResource ( r rest . Creater , scope * RequestScope , admission admission . Interface ) http . HandlerFunc {
2019-01-12 04:58:27 +00:00
return createHandler ( & namedCreaterAdapter { r } , scope , admission , false )
}
type namedCreaterAdapter struct {
rest . Creater
}
func ( c * namedCreaterAdapter ) Create ( ctx context . Context , name string , obj runtime . Object , createValidatingAdmission rest . ValidateObjectFunc , options * metav1 . CreateOptions ) ( runtime . Object , error ) {
return c . Creater . Create ( ctx , obj , createValidatingAdmission , options )
}
2019-04-07 17:07:55 +00:00
// manager is assumed to be already a valid value, we need to make
// userAgent into a valid value too.
func managerOrUserAgent ( manager , userAgent string ) string {
if manager != "" {
return manager
}
return prefixFromUserAgent ( userAgent )
}
// prefixFromUserAgent takes the characters preceding the first /, quote
// unprintable character and then trim what's beyond the
// FieldManagerMaxLength limit.
func prefixFromUserAgent ( u string ) string {
m := strings . Split ( u , "/" ) [ 0 ]
buf := bytes . NewBuffer ( nil )
for _ , r := range m {
// Ignore non-printable characters
if ! unicode . IsPrint ( r ) {
continue
}
// Only append if we have room for it
if buf . Len ( ) + utf8 . RuneLen ( r ) > validation . FieldManagerMaxLength {
break
}
buf . WriteRune ( r )
}
return buf . String ( )
}