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"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
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
)
func createHandler ( r rest . NamedCreater , scope RequestScope , admit admission . Interface , includeName bool ) http . HandlerFunc {
return func ( w http . ResponseWriter , req * http . Request ) {
// For performance tracking purposes.
trace := utiltrace . New ( "Create " + req . URL . Path )
defer trace . LogIfLong ( 500 * time . Millisecond )
if isDryRun ( req . URL ) && ! utilfeature . DefaultFeatureGate . Enabled ( features . DryRun ) {
scope . err ( errors . NewBadRequest ( "the dryRun alpha feature is disabled" ) , w , req )
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" ) )
var (
namespace , name string
err error
)
if includeName {
namespace , name , err = scope . Namer . Name ( req )
} else {
namespace , err = scope . Namer . Namespace ( req )
}
if err != nil {
scope . err ( err , w , req )
return
}
ctx := req . Context ( )
ctx = request . WithNamespace ( ctx , namespace )
2019-04-07 17:07:55 +00:00
outputMediaType , _ , err := negotiation . NegotiateOutputMediaType ( req , scope . Serializer , & scope )
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 ( )
if err := metainternalversion . ParameterCodec . DecodeParameters ( values , scope . MetaGroupVersion , options ) ; err != nil {
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
}
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 )
admissionAttributes := admission . NewAttributesRecord ( obj , nil , scope . Kind , namespace , name , scope . Resource , scope . Subresource , admission . Create , dryrun . IsDryRun ( options . DryRun ) , userInfo )
if mutatingAdmission , ok := admit . ( admission . MutationInterface ) ; ok && mutatingAdmission . Handles ( admission . Create ) {
2019-04-07 17:07:55 +00:00
err = mutatingAdmission . Admit ( admissionAttributes , & scope )
2019-01-12 04:58:27 +00:00
if err != nil {
scope . err ( err , w , req )
return
}
}
trace . Step ( "About to store object in database" )
result , err := finishRequest ( timeout , func ( ) ( runtime . Object , error ) {
return r . Create (
ctx ,
name ,
obj ,
2019-04-07 17:07:55 +00:00
rest . AdmissionToValidateObjectFunc ( admit , admissionAttributes , & scope ) ,
2019-01-12 04:58:27 +00:00
options ,
)
} )
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 )
}
scope . Trace = trace
2019-04-07 17:07:55 +00:00
transformResponseObject ( ctx , scope , 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.
func CreateNamedResource ( r rest . NamedCreater , scope RequestScope , admission admission . Interface ) http . HandlerFunc {
return createHandler ( r , scope , admission , true )
}
// CreateResource returns a function that will handle a resource creation.
func CreateResource ( r rest . Creater , scope RequestScope , admission admission . Interface ) http . HandlerFunc {
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 ( )
}