blob: de242771db97c5c89bebd29c5597adf17ca12db0 [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/runtime"
27 "k8s.io/apimachinery/pkg/runtime/schema"
28 "k8s.io/apiserver/pkg/admission"
29 "k8s.io/apiserver/pkg/audit"
30 "k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
31 "k8s.io/apiserver/pkg/endpoints/request"
32 "k8s.io/apiserver/pkg/registry/rest"
33 utiltrace "k8s.io/apiserver/pkg/util/trace"
34)
35
36// UpdateResource returns a function that will handle a resource update
37func UpdateResource(r rest.Updater, scope RequestScope, admit admission.Interface) http.HandlerFunc {
38 return func(w http.ResponseWriter, req *http.Request) {
39 // For performance tracking purposes.
40 trace := utiltrace.New("Update " + req.URL.Path)
41 defer trace.LogIfLong(500 * time.Millisecond)
42
43 if isDryRun(req.URL) {
44 scope.err(errors.NewBadRequest("dryRun is not supported yet"), w, req)
45 return
46 }
47
48 // 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)
49 timeout := parseTimeout(req.URL.Query().Get("timeout"))
50
51 namespace, name, err := scope.Namer.Name(req)
52 if err != nil {
53 scope.err(err, w, req)
54 return
55 }
56 ctx := req.Context()
57 ctx = request.WithNamespace(ctx, namespace)
58
59 body, err := readBody(req)
60 if err != nil {
61 scope.err(err, w, req)
62 return
63 }
64
65 s, err := negotiation.NegotiateInputSerializer(req, false, scope.Serializer)
66 if err != nil {
67 scope.err(err, w, req)
68 return
69 }
70 defaultGVK := scope.Kind
71 original := r.New()
72 trace.Step("About to convert to expected version")
73 decoder := scope.Serializer.DecoderToVersion(s.Serializer, schema.GroupVersion{Group: defaultGVK.Group, Version: runtime.APIVersionInternal})
74 obj, gvk, err := decoder.Decode(body, &defaultGVK, original)
75 if err != nil {
76 err = transformDecodeError(scope.Typer, err, original, gvk, body)
77 scope.err(err, w, req)
78 return
79 }
80 if gvk.GroupVersion() != defaultGVK.GroupVersion() {
81 err = errors.NewBadRequest(fmt.Sprintf("the API version in the data (%s) does not match the expected API version (%s)", gvk.GroupVersion(), defaultGVK.GroupVersion()))
82 scope.err(err, w, req)
83 return
84 }
85 trace.Step("Conversion done")
86
87 ae := request.AuditEventFrom(ctx)
88 audit.LogRequestObject(ae, obj, scope.Resource, scope.Subresource, scope.Serializer)
89 admit = admission.WithAudit(admit, ae)
90
91 if err := checkName(obj, name, namespace, scope.Namer); err != nil {
92 scope.err(err, w, req)
93 return
94 }
95
96 userInfo, _ := request.UserFrom(ctx)
97 staticAdmissionAttributes := admission.NewAttributesRecord(nil, nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)
98 var transformers []rest.TransformFunc
99 if mutatingAdmission, ok := admit.(admission.MutationInterface); ok && mutatingAdmission.Handles(admission.Update) {
100 transformers = append(transformers, func(ctx context.Context, newObj, oldObj runtime.Object) (runtime.Object, error) {
101 return newObj, mutatingAdmission.Admit(admission.NewAttributesRecord(newObj, oldObj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo))
102 })
103 }
104
105 trace.Step("About to store object in database")
106 wasCreated := false
107 result, err := finishRequest(timeout, func() (runtime.Object, error) {
108 obj, created, err := r.Update(
109 ctx,
110 name,
111 rest.DefaultUpdatedObjectInfo(obj, transformers...),
112 rest.AdmissionToValidateObjectFunc(admit, staticAdmissionAttributes),
113 rest.AdmissionToValidateObjectUpdateFunc(admit, staticAdmissionAttributes),
114 )
115 wasCreated = created
116 return obj, err
117 })
118 if err != nil {
119 scope.err(err, w, req)
120 return
121 }
122 trace.Step("Object stored in database")
123
124 requestInfo, ok := request.RequestInfoFrom(ctx)
125 if !ok {
126 scope.err(fmt.Errorf("missing requestInfo"), w, req)
127 return
128 }
129 if err := setSelfLink(result, requestInfo, scope.Namer); err != nil {
130 scope.err(err, w, req)
131 return
132 }
133 trace.Step("Self-link added")
134
135 status := http.StatusOK
136 if wasCreated {
137 status = http.StatusCreated
138 }
139
140 transformResponseObject(ctx, scope, req, w, status, result)
141 }
142}