blob: 54276007826b50daf18b966b373fc3fd3a1e4871 [file] [log] [blame]
Matthias Andreas Benkard832a54e2019-01-29 09:27:38 +01001/*
2Copyright 2017 The Kubernetes Authors.
3
4Licensed under the Apache License, Version 2.0 (the "License");
5you may not use this file except in compliance with the License.
6You may obtain a copy of the License at
7
8 http://www.apache.org/licenses/LICENSE-2.0
9
10Unless required by applicable law or agreed to in writing, software
11distributed under the License is distributed on an "AS IS" BASIS,
12WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13See the License for the specific language governing permissions and
14limitations under the License.
15*/
16
17package handlers
18
19import (
20 "context"
21 "fmt"
22 "net/http"
23 "time"
24
25 "k8s.io/apimachinery/pkg/api/errors"
26 "k8s.io/apimachinery/pkg/api/meta"
27 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
28 "k8s.io/apimachinery/pkg/runtime"
29 "k8s.io/apimachinery/pkg/runtime/schema"
30 "k8s.io/apiserver/pkg/admission"
31 "k8s.io/apiserver/pkg/audit"
32 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
33 "k8s.io/apiserver/pkg/endpoints/request"
34 "k8s.io/apiserver/pkg/registry/rest"
35 utiltrace "k8s.io/apiserver/pkg/util/trace"
36)
37
38func createHandler(r rest.NamedCreater, scope RequestScope, admit admission.Interface, includeName bool) http.HandlerFunc {
39 return func(w http.ResponseWriter, req *http.Request) {
40 // For performance tracking purposes.
41 trace := utiltrace.New("Create " + req.URL.Path)
42 defer trace.LogIfLong(500 * time.Millisecond)
43
44 if isDryRun(req.URL) {
45 scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
46 return
47 }
48
49 // 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)
50 timeout := parseTimeout(req.URL.Query().Get("timeout"))
51
52 var (
53 namespace, name string
54 err error
55 )
56 if includeName {
57 namespace, name, err = scope.Namer.Name(req)
58 } else {
59 namespace, err = scope.Namer.Namespace(req)
60 }
61 if err != nil {
62 scope.err(err, w, req)
63 return
64 }
65
66 ctx := req.Context()
67 ctx = request.WithNamespace(ctx, namespace)
68
69 gv := scope.Kind.GroupVersion()
70 s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
71 if err != nil {
72 scope.err(err, w, req)
73 return
74 }
75 decoder := scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal})
76
77 body, err := readBody(req)
78 if err != nil {
79 scope.err(err, w, req)
80 return
81 }
82
83 defaultGVK := scope.Kind
84 original := r.New()
85 trace.Step("About to convert to expected version")
86 obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
87 if err != nil {
88 err = transformDecodeError(scope.Typer, err, original, gvk, body)
89 scope.err(err, w, req)
90 return
91 }
92 if gvk.GroupVersion() != gv {
93 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()))
94 scope.err(err, w, req)
95 return
96 }
97 trace.Step("Conversion done")
98
99 ae := request.AuditEventFrom(ctx)
100 admit = admission.WithAudit(admit, ae)
101 audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
102
103 userInfo, _ := request.UserFrom(ctx)
104 admissionAttributes := admission.NewAttributesRecord(obj, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)
105 if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Create) {
106 err = mutatingAdmission.Admit(admissionAttributes)
107 if err != nil {
108 scope.err(err, w, req)
109 return
110 }
111 }
112
113 // TODO: replace with content type negotiation?
114 includeUninitialized := req.URL.Query().Get("includeUninitialized") == "1"
115
116 trace.Step("About to store object in database")
117 result, err := finishRequest(timeout, func() (runtime.Object, error) {
118 return r.Create(
119 ctx,
120 name,
121 obj,
122 rest.AdmissionToValidateObjectFunc(admit, admissionAttributes),
123 includeUninitialized,
124 )
125 })
126 if err != nil {
127 scope.err(err, w, req)
128 return
129 }
130 trace.Step("Object stored in database")
131
132 requestInfo, ok := request.RequestInfoFrom(ctx)
133 if !ok {
134 scope.err(fmt.Errorf("missing requestInfo"), w, req)
135 return
136 }
137 if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
138 scope.err(err, w, req)
139 return
140 }
141 trace.Step("Self-link added")
142
143 // If the object is partially initialized, always indicate it via StatusAccepted
144 code := http.StatusCreated
145 if accessor, err := meta.Accessor(result); err == nil {
146 if accessor.GetInitializers() != nil {
147 code = http.StatusAccepted
148 }
149 }
150 status, ok := result.(*metav1.Status)
151 if ok && err == nil && status.Code == 0 {
152 status.Code = int32(code)
153 }
154
155 transformResponseObject(ctx, scope, req, w, code, result)
156 }
157}
158
159// CreateNamedResource returns a function that will handle a resource creation with name.
160func CreateNamedResource(r rest.NamedCreater, scope RequestScope, admission admission.Interface) http.HandlerFunc {
161 return createHandler(r, scope, admission, true)
162}
163
164// CreateResource returns a function that will handle a resource creation.
165func CreateResource(r rest.Creater, scope RequestScope, admission admission.Interface) http.HandlerFunc {
166 return createHandler(&namedCreaterAdapter{r}, scope, admission, false)
167}
168
169type namedCreaterAdapter struct {
170 rest.Creater
171}
172
173func (c *namedCreaterAdapter) Create(ctx context.Context, name string, obj runtime.Object, createValidatingAdmission rest.ValidateObjectFunc, includeUninitialized bool) (runtime.Object, error) {
174 return c.Creater.Create(ctx, obj, createValidatingAdmission, includeUninitialized)
175}