func admit(t *testing.T, ir admission.Interface, pods []*api.Pod) { for i := range pods { p := pods[i] if err := ir.Admit(admission.NewAttributesRecord(p, nil, api.Kind("Pod").WithVersion("version"), "test", p.ObjectMeta.Name, api.Resource("pods").WithVersion("version"), "", admission.Create, nil)); err != nil { t.Error(err) } } }
func admit(t *testing.T, ir admission.Interface, pods []*api.Pod) { for i := range pods { p := pods[i] if err := ir.Admit(admission.NewAttributesRecord(p, "Pod", "test", p.ObjectMeta.Name, "pods", "", admission.Create, nil)); err != nil { t.Error(err) } } }
// testSCCAdmission is a helper to admit the pod and ensure it was validated against the expected // SCC. func testSCCAdmission(pod *kapi.Pod, plugin kadmission.Interface, expectedSCC string, t *testing.T) { attrs := kadmission.NewAttributesRecord(pod, kapi.Kind("Pod").WithVersion("version"), "namespace", "", kapi.Resource("pods").WithVersion("version"), "", kadmission.Create, &user.DefaultInfo{}) err := plugin.Admit(attrs) if err != nil { t.Errorf("error admitting pod: %v", err) return } validatedSCC, ok := pod.Annotations[allocator.ValidatedSCCAnnotation] if !ok { t.Errorf("expected to find the validated annotation on the pod for the scc but found none") return } if validatedSCC != expectedSCC { t.Errorf("should have validated against %s but found %s", expectedSCC, validatedSCC) } }
// ConnectResource returns a function that handles a connect request on a rest.Storage object. func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, connectOptionsKind, restPath string, subpath bool, subpathKey string) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) opts, err := getRequestOptions(req, scope, connectOptionsKind, subpath, subpathKey) if err != nil { errorJSON(err, scope.Codec, w) return } if admit.Handles(admission.Connect) { connectRequest := &rest.ConnectRequest{ Name: name, Options: opts, ResourcePath: restPath, } userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(connectRequest, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Connect, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } handler, err := connecter.Connect(ctx, name, opts) if err != nil { errorJSON(err, scope.Codec, w) return } handler.ServeHTTP(w, req.Request) err = handler.RequestError() if err != nil { errorJSON(err, scope.Codec, w) return } } }
// ConnectResource returns a function that handles a connect request on a rest.Storage object. func ConnectResource(connecter rest.Connecter, scope RequestScope, admit admission.Interface, restPath string) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter namespace, name, err := scope.Namer.Name(req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) opts, subpath, subpathKey := connecter.NewConnectOptions() if err := getRequestOptions(req, scope, opts, subpath, subpathKey); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if admit.Handles(admission.Connect) { connectRequest := &rest.ConnectRequest{ Name: name, Options: opts, ResourcePath: restPath, } userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(connectRequest, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Connect, userInfo)) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } handler, err := connecter.Connect(ctx, name, opts, &responder{scope: scope, req: req, res: res}) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } handler.ServeHTTP(w, req.Request) } }
// PatchResource returns a function that will handle a resource patch // TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) // PATCH requires same permission as UPDATE if admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } versionedObj, err := converter.ConvertToVersion(r.New(), scope.APIVersion) if err != nil { errorJSON(err, scope.Codec, w) return } contentType := req.HeaderParameter("Content-Type") // Remove "; charset=" if included in header. if idx := strings.Index(contentType, ";"); idx > 0 { contentType = contentType[:idx] } patchType := api.PatchType(contentType) patchJS, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } result, err := patchResource(ctx, timeout, versionedObj, r, name, patchType, patchJS, scope.Namer, scope.Codec) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }
func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.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 { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) body, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() if err := scope.Codec.DecodeIntoWithSpecifiedVersionKind(body, obj, scope.APIVersion, scope.Kind); err != nil { err = transformDecodeError(typer, err, obj, body) errorJSON(err, scope.Codec, w) return } if admit != nil && admit.Handles(admission.Create) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } result, err := finishRequest(timeout, func() (runtime.Object, error) { out, err := r.Create(ctx, name, obj) if status, ok := out.(*unversioned.Status); ok && err == nil && status.Code == 0 { status.Code = http.StatusCreated } return out, err }) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusCreated, scope.APIVersion, scope.Codec, result, w, req.Request) } }
// DeleteCollection returns a function that will handle a collection deletion func DeleteCollection(r rest.CollectionDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, err := scope.Namer.Namespace(req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) if admit != nil && admit.Handles(admission.Delete) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind, namespace, "", scope.Resource, scope.Subresource, admission.Delete, userInfo)) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } listOptions := api.ListOptions{} if err := scope.ParameterCodec.DecodeParameters(req.Request.URL.Query(), scope.Kind.GroupVersion(), &listOptions); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // transform fields // TODO: DecodeParametersInto should do this. if listOptions.FieldSelector != nil { fn := func(label, value string) (newLabel, newValue string, err error) { return scope.Convertor.ConvertFieldLabel(scope.Kind.GroupVersion().String(), scope.Kind.Kind, label, value) } if listOptions.FieldSelector, err = listOptions.FieldSelector.Transform(fn); err != nil { // TODO: allow bad request to set field causes based on query parameters err = errors.NewBadRequest(err.Error()) scope.err(err, res.ResponseWriter, req.Request) return } } options := &api.DeleteOptions{} if checkBody { body, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if len(body) > 0 { s, err := negotiateInputSerializer(req.Request, scope.Serializer) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions") obj, _, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if obj != options { scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), res.ResponseWriter, req.Request) return } } } result, err := finishRequest(timeout, func() (runtime.Object, error) { return r.DeleteCollection(ctx, options, &listOptions) }) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid // object with the response. if result == nil { result = &unversioned.Status{ Status: unversioned.StatusSuccess, Code: http.StatusOK, Details: &unversioned.StatusDetails{ Kind: scope.Kind.Kind, }, } } else { // when a non-status response is returned, set the self link if _, ok := result.(*unversioned.Status); !ok { if _, err := setListSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } } writeNegotiated(scope.Serializer, scope.Kind.GroupVersion(), w, req.Request, http.StatusOK, result) } }
// DeleteResource returns a function that will handle a resource deletion func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { // For performance tracking purposes. trace := util.NewTrace("Delete " + req.Request.URL.Path) defer trace.LogIfLong(250 * time.Millisecond) w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) options := &api.DeleteOptions{} if checkBody { body, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if len(body) > 0 { s, err := negotiateInputSerializer(req.Request, scope.Serializer) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } defaultGVK := scope.Kind.GroupVersion().WithKind("DeleteOptions") obj, _, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, options) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if obj != options { scope.err(fmt.Errorf("decoded object cannot be converted to DeleteOptions"), res.ResponseWriter, req.Request) return } } } if admit != nil && admit.Handles(admission.Delete) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } trace.Step("About do delete object from database") result, err := finishRequest(timeout, func() (runtime.Object, error) { return r.Delete(ctx, name, options) }) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Object deleted from database") // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid // object with the response. if result == nil { result = &unversioned.Status{ Status: unversioned.StatusSuccess, Code: http.StatusOK, Details: &unversioned.StatusDetails{ Name: name, Kind: scope.Kind.Kind, }, } } else { // when a non-status response is returned, set the self link if _, ok := result.(*unversioned.Status); !ok { if err := setSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } } write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) } }
// UpdateResource returns a function that will handle a resource update func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { // For performance tracking purposes. trace := util.NewTrace("Update " + req.Request.URL.Path) defer trace.LogIfLong(250 * time.Millisecond) w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) body, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } s, err := negotiateInputSerializer(req.Request, scope.Serializer) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } defaultGVK := scope.Kind original := r.New() trace.Step("About to convert to expected version") obj, gvk, err := scope.Serializer.DecoderToVersion(s, defaultGVK.GroupVersion()).Decode(body, &defaultGVK, original) if err != nil { err = transformDecodeError(typer, err, original, gvk, body) scope.err(err, res.ResponseWriter, req.Request) 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, res.ResponseWriter, req.Request) return } trace.Step("Conversion done") if err := checkName(obj, name, namespace, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if admit != nil && admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } trace.Step("About to store object in database") wasCreated := false result, err := finishRequest(timeout, func() (runtime.Object, error) { obj, created, err := r.Update(ctx, obj) wasCreated = created return obj, err }) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Object stored in database") if err := setSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Self-link added") status := http.StatusOK if wasCreated { status = http.StatusCreated } write(status, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) } }
// PatchResource returns a function that will handle a resource patch // TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion()) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } // TODO: handle this in negotiation contentType := req.HeaderParameter("Content-Type") // Remove "; charset=" if included in header. if idx := strings.Index(contentType, ";"); idx > 0 { contentType = contentType[:idx] } patchType := api.PatchType(contentType) patchJS, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } s, ok := scope.Serializer.SerializerForMediaType("application/json", nil) if !ok { scope.err(fmt.Errorf("no serializer defined for JSON"), res.ResponseWriter, req.Request) return } gv := scope.Kind.GroupVersion() codec := runtime.NewCodec( scope.Serializer.EncoderForVersion(s, gv), scope.Serializer.DecoderToVersion(s, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}), ) updateAdmit := func(updatedObject runtime.Object) error { if admit != nil && admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) return admit.Admit(admission.NewAttributesRecord(updatedObject, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) } return nil } result, err := patchResource(ctx, updateAdmit, timeout, versionedObj, r, name, patchType, patchJS, scope.Namer, codec) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } if err := setSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } write(http.StatusOK, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) } }
// UpdateResource returns a function that will handle a resource update func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { // For performance tracking purposes. trace := util.NewTrace("Update " + req.Request.URL.Path) defer trace.LogIfLong(250 * time.Millisecond) w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) body, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() trace.Step("About to convert to expected version") if err := scope.Codec.DecodeIntoWithSpecifiedVersionKind(body, obj, scope.Kind); err != nil { err = transformDecodeError(typer, err, obj, body) errorJSON(err, scope.Codec, w) return } trace.Step("Conversion done") if err := checkName(obj, name, namespace, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } if admit != nil && admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Update, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } trace.Step("About to store object in database") wasCreated := false result, err := finishRequest(timeout, func() (runtime.Object, error) { obj, created, err := r.Update(ctx, obj) wasCreated = created return obj, err }) if err != nil { errorJSON(err, scope.Codec, w) return } trace.Step("Object stored in database") if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } trace.Step("Self-link added") status := http.StatusOK if wasCreated { status = http.StatusCreated } writeJSON(status, scope.Codec, result, w, isPrettyPrint(req.Request)) } }
func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { // For performance tracking purposes. trace := util.NewTrace("Create " + req.Request.URL.Path) defer trace.LogIfLong(250 * time.Millisecond) w := res.ResponseWriter // 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.Request.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 { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) body, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() trace.Step("About to convert to expected version") // TODO this cleans up with proper typing if err := scope.Codec.DecodeIntoWithSpecifiedVersionKind(body, obj, scope.Kind); err != nil { err = transformDecodeError(typer, err, obj, body) errorJSON(err, scope.Codec, w) return } trace.Step("Conversion done") if admit != nil && admit.Handles(admission.Create) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Create, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } trace.Step("About to store object in database") result, err := finishRequest(timeout, func() (runtime.Object, error) { out, err := r.Create(ctx, name, obj) if status, ok := out.(*unversioned.Status); ok && err == nil && status.Code == 0 { status.Code = http.StatusCreated } return out, err }) if err != nil { errorJSON(err, scope.Codec, w) return } trace.Step("Object stored in database") if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } trace.Step("Self-link added") write(http.StatusCreated, scope.Kind.GroupVersion(), scope.Codec, result, w, req.Request) } }
// PatchResource returns a function that will handle a resource patch // TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) if admit != nil && admit.Handles(admission.Patch) { userInfo, _ := api.UserFrom(ctx) // TODO this record should include the patch itself. Right now we can't do that because patches are not runtime.Objects err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Patch, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } versionedObj, err := converter.ConvertToVersion(r.New(), scope.Kind.GroupVersion().String()) if err != nil { errorJSON(err, scope.Codec, w) return } contentType := req.HeaderParameter("Content-Type") // Remove "; charset=" if included in header. if idx := strings.Index(contentType, ";"); idx > 0 { contentType = contentType[:idx] } patchType := api.PatchType(contentType) patchJS, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } updateAdmit := func(updatedObject runtime.Object) error { if admit != nil && admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) return admit.Admit(admission.NewAttributesRecord(updatedObject, scope.Kind.GroupKind(), namespace, name, scope.Resource.GroupResource(), scope.Subresource, admission.Update, userInfo)) } return nil } result, err := patchResource(ctx, updateAdmit, timeout, versionedObj, r, name, patchType, patchJS, scope.Namer, scope.Codec) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusOK, scope.Kind.GroupVersion(), scope.Codec, result, w, req.Request) } }
// PatchResource returns a function that will handle a resource patch // TODO: Eventually PatchResource should just use GuaranteedUpdate and this routine should be a bit cleaner func PatchResource(r rest.Patcher, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, converter runtime.ObjectConvertor) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) // PATCH requires same permission as UPDATE if admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } versionedObj, err := converter.ConvertToVersion(obj, scope.APIVersion) if err != nil { errorJSON(err, scope.Codec, w) return } original, err := r.Get(ctx, name) if err != nil { errorJSON(err, scope.Codec, w) return } originalObjJS, err := scope.Codec.Encode(original) if err != nil { errorJSON(err, scope.Codec, w) return } patchJS, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } contentType := req.HeaderParameter("Content-Type") patchedObjJS, err := getPatchedJS(contentType, originalObjJS, patchJS, versionedObj) if err != nil { errorJSON(err, scope.Codec, w) return } if err := scope.Codec.DecodeInto(patchedObjJS, obj); err != nil { errorJSON(err, scope.Codec, w) return } if err := checkName(obj, name, namespace, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } result, err := finishRequest(timeout, func() (runtime.Object, error) { // update should never create as previous get would fail obj, _, err := r.Update(ctx, obj) return obj, err }) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }
// UpdateResource returns a function that will handle a resource update func UpdateResource(r rest.Updater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) body, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } obj := r.New() if err := scope.Codec.DecodeIntoWithSpecifiedVersionKind(body, obj, scope.APIVersion, scope.Kind); err != nil { err = transformDecodeError(typer, err, obj, body) errorJSON(err, scope.Codec, w) return } if err := checkName(obj, name, namespace, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } if admit != nil && admit.Handles(admission.Update) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Update, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } wasCreated := false result, err := finishRequest(timeout, func() (runtime.Object, error) { obj, created, err := r.Update(ctx, obj) wasCreated = created return obj, err }) if err != nil { errorJSON(err, scope.Codec, w) return } if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } status := http.StatusOK if wasCreated { status = http.StatusCreated } writeJSON(status, scope.Codec, result, w, isPrettyPrint(req.Request)) } }
// DeleteResource returns a function that will handle a resource deletion func DeleteResource(r rest.GracefulDeleter, checkBody bool, scope RequestScope, admit admission.Interface) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { w := res.ResponseWriter // 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.Request.URL.Query().Get("timeout")) namespace, name, err := scope.Namer.Name(req) if err != nil { errorJSON(err, scope.Codec, w) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) options := &api.DeleteOptions{} if checkBody { body, err := readBody(req.Request) if err != nil { errorJSON(err, scope.Codec, w) return } if len(body) > 0 { if err := scope.Codec.DecodeInto(body, options); err != nil { errorJSON(err, scope.Codec, w) return } } } if admit != nil && admit.Handles(admission.Delete) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(nil, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Delete, userInfo)) if err != nil { errorJSON(err, scope.Codec, w) return } } result, err := finishRequest(timeout, func() (runtime.Object, error) { return r.Delete(ctx, name, options) }) if err != nil { errorJSON(err, scope.Codec, w) return } // if the rest.Deleter returns a nil object, fill out a status. Callers may return a valid // object with the response. if result == nil { result = &unversioned.Status{ Status: unversioned.StatusSuccess, Code: http.StatusOK, Details: &unversioned.StatusDetails{ Name: name, Kind: scope.Kind, }, } } else { // when a non-status response is returned, set the self link if _, ok := result.(*unversioned.Status); !ok { if err := setSelfLink(result, req, scope.Namer); err != nil { errorJSON(err, scope.Codec, w) return } } } write(http.StatusOK, scope.APIVersion, scope.Codec, result, w, req.Request) } }
func createHandler(r rest.NamedCreater, scope RequestScope, typer runtime.ObjectTyper, admit admission.Interface, includeName bool) restful.RouteFunction { return func(req *restful.Request, res *restful.Response) { // For performance tracking purposes. trace := util.NewTrace("Create " + req.Request.URL.Path) defer trace.LogIfLong(250 * time.Millisecond) w := res.ResponseWriter // 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.Request.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, res.ResponseWriter, req.Request) return } ctx := scope.ContextFunc(req) ctx = api.WithNamespace(ctx, namespace) gv := scope.Kind.GroupVersion() s, err := negotiateInputSerializer(req.Request, scope.Serializer) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } decoder := scope.Serializer.DecoderToVersion(s, unversioned.GroupVersion{Group: gv.Group, Version: runtime.APIVersionInternal}) body, err := readBody(req.Request) if err != nil { scope.err(err, res.ResponseWriter, req.Request) 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(typer, err, original, gvk, body) scope.err(err, res.ResponseWriter, req.Request) 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, res.ResponseWriter, req.Request) return } trace.Step("Conversion done") if admit != nil && admit.Handles(admission.Create) { userInfo, _ := api.UserFrom(ctx) err = admit.Admit(admission.NewAttributesRecord(obj, scope.Kind, namespace, name, scope.Resource, scope.Subresource, admission.Create, userInfo)) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } } trace.Step("About to store object in database") result, err := finishRequest(timeout, func() (runtime.Object, error) { out, err := r.Create(ctx, name, obj) if status, ok := out.(*unversioned.Status); ok && err == nil && status.Code == 0 { status.Code = http.StatusCreated } return out, err }) if err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Object stored in database") if err := setSelfLink(result, req, scope.Namer); err != nil { scope.err(err, res.ResponseWriter, req.Request) return } trace.Step("Self-link added") write(http.StatusCreated, scope.Kind.GroupVersion(), scope.Serializer, result, w, req.Request) } }