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:
Kubernetes Submit Queue
2018-05-23 04:05:59 -07:00
committed by GitHub
6 changed files with 53 additions and 16 deletions

View File

@@ -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.

View File

@@ -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"
)

View File

@@ -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",
],
)

View File

@@ -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)
}

View File

@@ -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),
}

View File

@@ -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).