mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-07-20 07:56:02 +00:00
Merge pull request #63515 from jennybuckley/apply-content-type
Automatic merge from submit-queue. [feature branch] Implement first iteration of serverside apply as a PUT **What this PR does / why we need it**: Adds new apply content type for patch: "application/apply-patch+yaml" (This will also accept json because json is a subset of yaml) Implements first iteration of apply which just performs a PUT operation with the body of the request. This is for the "feature-serverside-apply" feature branch. Large number of line changes because all the generated stuff needs to be updated to include the new content type ```release-note NONE ``` /sig api-machinery /kind feature /cc @apelisse @seans3 @lavalamp
This commit is contained in:
@@ -42,6 +42,7 @@ type TypeMeta struct {
|
||||
|
||||
const (
|
||||
ContentTypeJSON string = "application/json"
|
||||
ContentTypeYAML string = "application/yaml"
|
||||
)
|
||||
|
||||
// RawExtension is used to hold extensions in external versions.
|
||||
|
||||
@@ -25,4 +25,5 @@ const (
|
||||
JSONPatchType PatchType = "application/json-patch+json"
|
||||
MergePatchType PatchType = "application/merge-patch+json"
|
||||
StrategicMergePatchType PatchType = "application/strategic-merge-patch+json"
|
||||
ApplyPatchType PatchType = "application/apply-patch+yaml"
|
||||
)
|
||||
|
||||
@@ -77,8 +77,10 @@ go_library(
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/handlers/negotiation:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/endpoints/metrics:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/features:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/registry/rest:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/server/filters:go_default_library",
|
||||
"//vendor/k8s.io/apiserver/pkg/util/feature:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
|
||||
@@ -83,19 +83,23 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
||||
return
|
||||
}
|
||||
|
||||
patchJS, err := readBody(req)
|
||||
patchBytes, err := readBody(req)
|
||||
if err != nil {
|
||||
scope.err(err, w, req)
|
||||
return
|
||||
}
|
||||
|
||||
ae := request.AuditEventFrom(ctx)
|
||||
audit.LogRequestPatch(ae, patchJS)
|
||||
audit.LogRequestPatch(ae, patchBytes)
|
||||
trace.Step("Recorded the audit event")
|
||||
|
||||
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), runtime.ContentTypeJSON)
|
||||
baseContentType := runtime.ContentTypeJSON
|
||||
if patchType == types.ApplyPatchType {
|
||||
baseContentType = runtime.ContentTypeYAML
|
||||
}
|
||||
s, ok := runtime.SerializerInfoForMediaType(scope.Serializer.SupportedMediaTypes(), baseContentType)
|
||||
if !ok {
|
||||
scope.err(fmt.Errorf("no serializer defined for JSON"), w, req)
|
||||
scope.err(fmt.Errorf("no serializer defined for %v", baseContentType), w, req)
|
||||
return
|
||||
}
|
||||
gv := scope.Kind.GroupVersion()
|
||||
@@ -134,7 +138,7 @@ func PatchResource(r rest.Patcher, scope RequestScope, admit admission.Interface
|
||||
restPatcher: r,
|
||||
name: name,
|
||||
patchType: patchType,
|
||||
patchJS: patchJS,
|
||||
patchBytes: patchBytes,
|
||||
|
||||
trace: trace,
|
||||
}
|
||||
@@ -193,7 +197,7 @@ type patcher struct {
|
||||
restPatcher rest.Patcher
|
||||
name string
|
||||
patchType types.PatchType
|
||||
patchJS []byte
|
||||
patchBytes []byte
|
||||
|
||||
trace *utiltrace.Trace
|
||||
|
||||
@@ -238,18 +242,18 @@ func (p *jsonPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (r
|
||||
return objToUpdate, nil
|
||||
}
|
||||
|
||||
// patchJS applies the patch. Input and output objects must both have
|
||||
// applyJSPatch applies the patch. Input and output objects must both have
|
||||
// the external version, since that is what the patch must have been constructed against.
|
||||
func (p *jsonPatcher) applyJSPatch(versionedJS []byte) (patchedJS []byte, retErr error) {
|
||||
switch p.patchType {
|
||||
case types.JSONPatchType:
|
||||
patchObj, err := jsonpatch.DecodePatch(p.patchJS)
|
||||
patchObj, err := jsonpatch.DecodePatch(p.patchBytes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return patchObj.Apply(versionedJS)
|
||||
case types.MergePatchType:
|
||||
return jsonpatch.MergePatch(versionedJS, p.patchJS)
|
||||
return jsonpatch.MergePatch(versionedJS, p.patchBytes)
|
||||
default:
|
||||
// only here as a safety net - go-restful filters content-type
|
||||
return nil, fmt.Errorf("unknown Content-Type header for patch: %v", p.patchType)
|
||||
@@ -271,7 +275,7 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := strategicPatchObject(p.codec, p.defaulter, currentVersionedObject, p.patchJS, versionedObjToUpdate, p.schemaReferenceObj); err != nil {
|
||||
if err := strategicPatchObject(p.codec, p.defaulter, currentVersionedObject, p.patchBytes, versionedObjToUpdate, p.schemaReferenceObj); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// Convert the object back to unversioned (aka internal version).
|
||||
@@ -283,16 +287,16 @@ func (p *smpPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (ru
|
||||
return unversionedObjToUpdate, nil
|
||||
}
|
||||
|
||||
// strategicPatchObject applies a strategic merge patch of <patchJS> to
|
||||
// strategicPatchObject applies a strategic merge patch of <patchBytes> to
|
||||
// <originalObject> and stores the result in <objToUpdate>.
|
||||
// It additionally returns the map[string]interface{} representation of the
|
||||
// <originalObject> and <patchJS>.
|
||||
// <originalObject> and <patchBytes>.
|
||||
// NOTE: Both <originalObject> and <objToUpdate> are supposed to be versioned.
|
||||
func strategicPatchObject(
|
||||
codec runtime.Codec,
|
||||
defaulter runtime.ObjectDefaulter,
|
||||
originalObject runtime.Object,
|
||||
patchJS []byte,
|
||||
patchBytes []byte,
|
||||
objToUpdate runtime.Object,
|
||||
schemaReferenceObj runtime.Object,
|
||||
) error {
|
||||
@@ -302,7 +306,7 @@ func strategicPatchObject(
|
||||
}
|
||||
|
||||
patchMap := make(map[string]interface{})
|
||||
if err := json.Unmarshal(patchJS, &patchMap); err != nil {
|
||||
if err := json.Unmarshal(patchBytes, &patchMap); err != nil {
|
||||
return errors.NewBadRequest(err.Error())
|
||||
}
|
||||
|
||||
@@ -312,6 +316,27 @@ func strategicPatchObject(
|
||||
return nil
|
||||
}
|
||||
|
||||
type applyPatcher struct {
|
||||
*patcher
|
||||
}
|
||||
|
||||
func (p *applyPatcher) applyPatchToCurrentObject(currentObject runtime.Object) (runtime.Object, error) {
|
||||
versionedObjToUpdate, err := p.creater.New(p.kind)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := runtime.DecodeInto(p.codec, p.patchBytes, versionedObjToUpdate); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// For now, instead of performing a structured merge we will just attempt a PUT
|
||||
// So we just convert the object back to unversioned (aka internal version).
|
||||
unversionedObjToUpdate, err := p.toUnversioned(versionedObjToUpdate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return unversionedObjToUpdate, nil
|
||||
}
|
||||
|
||||
// applyPatch is called every time GuaranteedUpdate asks for the updated object,
|
||||
// and is given the currently persisted object as input.
|
||||
func (p *patcher) applyPatch(_ context.Context, _, currentObject runtime.Object) (runtime.Object, error) {
|
||||
@@ -348,6 +373,9 @@ func (p *patcher) patchResource(ctx context.Context) (runtime.Object, error) {
|
||||
p.mechanism = &jsonPatcher{patcher: p}
|
||||
case types.StrategicMergePatchType:
|
||||
p.mechanism = &smpPatcher{patcher: p}
|
||||
// this case is unreachable if ServerSideApply is not enabled because we will have already rejected the content type
|
||||
case types.ApplyPatchType:
|
||||
p.mechanism = &applyPatcher{patcher: p}
|
||||
default:
|
||||
return nil, fmt.Errorf("%v: unimplemented patch type", p.patchType)
|
||||
}
|
||||
|
||||
@@ -399,7 +399,7 @@ func (tc *patchTestCase) Run(t *testing.T) {
|
||||
restPatcher: testPatcher,
|
||||
name: name,
|
||||
patchType: patchType,
|
||||
patchJS: patch,
|
||||
patchBytes: patch,
|
||||
|
||||
trace: utiltrace.New("Patch" + name),
|
||||
}
|
||||
|
||||
@@ -37,8 +37,10 @@ import (
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers"
|
||||
"k8s.io/apiserver/pkg/endpoints/handlers/negotiation"
|
||||
"k8s.io/apiserver/pkg/endpoints/metrics"
|
||||
"k8s.io/apiserver/pkg/features"
|
||||
"k8s.io/apiserver/pkg/registry/rest"
|
||||
genericfilters "k8s.io/apiserver/pkg/server/filters"
|
||||
utilfeature "k8s.io/apiserver/pkg/util/feature"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -651,11 +653,14 @@ func (a *APIInstaller) registerResourceHandlers(path string, storage rest.Storag
|
||||
string(types.MergePatchType),
|
||||
string(types.StrategicMergePatchType),
|
||||
}
|
||||
if utilfeature.DefaultFeatureGate.Enabled(features.ServerSideApply) {
|
||||
supportedTypes = append(supportedTypes, string(types.ApplyPatchType))
|
||||
}
|
||||
handler := metrics.InstrumentRouteFunc(action.Verb, resource, subresource, requestScope, restfulPatchResource(patcher, reqScope, admit, supportedTypes))
|
||||
route := ws.PATCH(action.Path).To(handler).
|
||||
Doc(doc).
|
||||
Param(ws.QueryParameter("pretty", "If 'true', then the output is pretty printed.")).
|
||||
Consumes(string(types.JSONPatchType), string(types.MergePatchType), string(types.StrategicMergePatchType)).
|
||||
Consumes(supportedTypes...).
|
||||
Operation("patch"+namespaced+kind+strings.Title(subresource)+operationSuffix).
|
||||
Produces(append(storageMeta.ProducesMIMETypes(action.Verb), mediaTypes...)...).
|
||||
Returns(http.StatusOK, "OK", producedObject).
|
||||
|
||||
Reference in New Issue
Block a user