[cozystack-api] Disable startegic-json-patch support (#1179)

Signed-off-by: Andrei Kvapil <kvapss@gmail.com>

<!-- Thank you for making a contribution! Here are some tips for you:
- Start the PR title with the [label] of Cozystack component:
- For system components: [platform], [system], [linstor], [cilium],
[kube-ovn], [dashboard], [cluster-api], etc.
- For managed apps: [apps], [tenant], [kubernetes], [postgres],
[virtual-machine] etc.
- For development and maintenance: [tests], [ci], [docs], [maintenance].
- If it's a work in progress, consider creating this PR as a draft.
- Don't hesistate to ask for opinion and review in the community chats,
even if it's still a draft.
- Add the label `backport` if it's a bugfix that needs to be backported
to a previous version.
-->

## What this PR does

This PR adds a post-processing hook that removes
application/strategic-merge-patch+json from every PATCH operation in the
generated OpenAPI v2/v3 specs.

Strategic-merge-patch (SMP) is never supported for CRDs, and our
aggregated API implementation can’t handle it either. When the spec
advertises SMP, kubectl picks that media-type by default and sends an
SMP body, which the apiserver then rejects with
unable to find api field in struct JSON for the json field ….

By dropping SMP from consumes / content:
* kubectl apply|patch … transparently falls back to
application/merge-patch+json or application/json-patch+json.
* Server-side-apply (kubectl apply --server-side …) keeps working via
application/apply-patch+yaml.

No changes are required on the handler side—only the advertised
media-types are updated.


### Release note

<!--  Write a release note:
- Explain what has changed internally and for users.
- Start with the same [label] as in the PR title
- Follow the guidelines at
https://github.com/kubernetes/community/blob/master/contributors/guide/release-notes.md.
-->

```release-note
[cozystack-api] Disable startegic-json-patch support
```
This commit is contained in:
Andrei Kvapil
2025-07-09 18:34:43 +02:00
committed by GitHub

View File

@@ -16,6 +16,7 @@ import (
const (
baseRef = "com.github.cozystack.cozystack.pkg.apis.apps.v1alpha1.Application"
baseListRef = baseRef + "List"
smp = "application/strategic-merge-patch+json"
)
func deepCopySchema(in *spec.Schema) *spec.Schema {
@@ -101,21 +102,20 @@ func patchSpec(target *spec.Schema, raw string) error {
func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
return func(doc *spec3.OpenAPI) (*spec3.OpenAPI, error) {
// Replace the basic "Application" schema with the user-supplied kinds.
if doc.Components == nil {
doc.Components = &spec3.Components{}
}
if doc.Components.Schemas == nil {
doc.Components.Schemas = map[string]*spec.Schema{}
}
base, ok := doc.Components.Schemas[baseRef]
if !ok {
return doc, fmt.Errorf("base schema %q not found", baseRef)
}
for kind, raw := range kindSchemas {
ref := fmt.Sprintf("%s.%s", "com.github.cozystack.cozystack.pkg.apis.apps.v1alpha1", kind)
s := doc.Components.Schemas[ref]
if s == nil { // first time clone "Application"
s = deepCopySchema(base)
@@ -128,7 +128,6 @@ func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*sp
}
doc.Components.Schemas[ref] = s
}
container := findSpecContainer(s)
if container == nil { // fallback: use the root
container = s
@@ -137,9 +136,19 @@ func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*sp
return nil, fmt.Errorf("kind %s: %w", kind, err)
}
}
delete(doc.Components.Schemas, baseRef)
delete(doc.Components.Schemas, baseListRef)
// Disable strategic-merge-patch+json support in all PATCH operations
for p, pi := range doc.Paths.Paths {
if pi == nil || pi.Patch == nil || pi.Patch.RequestBody == nil {
continue
}
delete(pi.Patch.RequestBody.Content, smp)
doc.Paths.Paths[p] = pi
}
return doc, nil
}
}
@@ -150,15 +159,15 @@ func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*sp
func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spec.Swagger, error) {
return func(sw *spec.Swagger) (*spec.Swagger, error) {
// Replace the basic "Application" schema with the user-supplied kinds.
defs := sw.Definitions
base, ok := defs[baseRef]
if !ok {
return sw, fmt.Errorf("base schema %q not found", baseRef)
}
for kind, raw := range kindSchemas {
ref := fmt.Sprintf("%s.%s", "com.github.cozystack.cozystack.pkg.apis.apps.v1alpha1", kind)
s := deepCopySchema(&base)
s.Extensions = map[string]interface{}{
"x-kubernetes-group-version-kind": []interface{}{
@@ -167,13 +176,10 @@ func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spe
},
},
}
if err := patchSpec(s, raw); err != nil {
return nil, fmt.Errorf("kind %s: %w", kind, err)
}
defs[ref] = *s
// clone the List variant
listName := ref + "List"
listSrc := defs[baseListRef]
@@ -193,9 +199,23 @@ func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spe
}
defs[listName] = *listCopy
}
delete(defs, baseRef)
delete(defs, baseListRef)
// Disable strategic-merge-patch+json support in all PATCH operations
for p, op := range sw.Paths.Paths {
if op.Patch != nil && len(op.Patch.Consumes) > 0 {
var out []string
for _, c := range op.Patch.Consumes {
if c != smp {
out = append(out, c)
}
}
op.Patch.Consumes = out
sw.Paths.Paths[p] = op
}
}
return sw, nil
}
}