mirror of
https://github.com/outbackdingo/cozystack.git
synced 2026-01-27 18:18:41 +00:00
202 lines
5.7 KiB
Go
202 lines
5.7 KiB
Go
package server
|
||
|
||
import (
|
||
"encoding/json"
|
||
"fmt"
|
||
"strings"
|
||
|
||
"k8s.io/kube-openapi/pkg/spec3"
|
||
"k8s.io/kube-openapi/pkg/validation/spec"
|
||
)
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// shared helpers
|
||
// -----------------------------------------------------------------------------
|
||
|
||
const (
|
||
baseRef = "com.github.cozystack.cozystack.pkg.apis.apps.v1alpha1.Application"
|
||
baseListRef = baseRef + "List"
|
||
)
|
||
|
||
func deepCopySchema(in *spec.Schema) *spec.Schema {
|
||
if in == nil {
|
||
return nil
|
||
}
|
||
b, err := json.Marshal(in)
|
||
if err != nil {
|
||
// Log error or panic since this is unexpected
|
||
panic(fmt.Sprintf("failed to marshal schema: %v", err))
|
||
}
|
||
var out spec.Schema
|
||
if err := json.Unmarshal(b, &out); err != nil {
|
||
panic(fmt.Sprintf("failed to unmarshal schema: %v", err))
|
||
}
|
||
return &out
|
||
}
|
||
|
||
// find the object that already owns ".spec"
|
||
func findSpecContainer(s *spec.Schema) *spec.Schema {
|
||
if s == nil {
|
||
return nil
|
||
}
|
||
if len(s.Type) > 0 && s.Type.Contains("object") && s.Properties != nil {
|
||
if _, ok := s.Properties["spec"]; ok {
|
||
return s
|
||
}
|
||
}
|
||
for _, branch := range [][]spec.Schema{s.AllOf, s.OneOf, s.AnyOf} {
|
||
for i := range branch {
|
||
if res := findSpecContainer(&branch[i]); res != nil {
|
||
return res
|
||
}
|
||
}
|
||
}
|
||
return nil
|
||
}
|
||
|
||
// apply user-supplied schema; when raw == "" turn the field into a schemaless object
|
||
func patchSpec(target *spec.Schema, raw string) error {
|
||
// ------------------------------------------------------------------
|
||
// 1) schema not provided → make ".spec" a fully open object
|
||
// ------------------------------------------------------------------
|
||
if strings.TrimSpace(raw) == "" {
|
||
if target.Properties == nil {
|
||
target.Properties = map[string]spec.Schema{}
|
||
}
|
||
prop := target.Properties["spec"]
|
||
prop.AdditionalProperties = &spec.SchemaOrBool{
|
||
Allows: true,
|
||
Schema: &spec.Schema{},
|
||
}
|
||
target.Properties["spec"] = prop
|
||
return nil
|
||
}
|
||
|
||
// ------------------------------------------------------------------
|
||
// 2) custom schema provided → keep / inject additionalProperties
|
||
// ------------------------------------------------------------------
|
||
var custom spec.Schema
|
||
if err := json.Unmarshal([]byte(raw), &custom); err != nil {
|
||
return err
|
||
}
|
||
|
||
// if user didn't specify additionalProperties, add a permissive one
|
||
if custom.AdditionalProperties == nil {
|
||
custom.AdditionalProperties = &spec.SchemaOrBool{
|
||
Allows: true,
|
||
Schema: &spec.Schema{},
|
||
}
|
||
}
|
||
|
||
if target.Properties == nil {
|
||
target.Properties = map[string]spec.Schema{}
|
||
}
|
||
target.Properties["spec"] = custom
|
||
return nil
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// OpenAPI **v3** post-processor
|
||
// -----------------------------------------------------------------------------
|
||
func buildPostProcessV3(kindSchemas map[string]string) func(*spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||
|
||
return func(doc *spec3.OpenAPI) (*spec3.OpenAPI, error) {
|
||
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)
|
||
s.Extensions = map[string]interface{}{
|
||
"x-kubernetes-group-version-kind": []interface{}{
|
||
map[string]interface{}{
|
||
"group": "apps.cozystack.io", "version": "v1alpha1", "kind": kind,
|
||
},
|
||
},
|
||
}
|
||
doc.Components.Schemas[ref] = s
|
||
}
|
||
|
||
container := findSpecContainer(s)
|
||
if container == nil { // fallback: use the root
|
||
container = s
|
||
}
|
||
if err := patchSpec(container, raw); err != nil {
|
||
return nil, fmt.Errorf("kind %s: %w", kind, err)
|
||
}
|
||
}
|
||
|
||
delete(doc.Components.Schemas, baseRef)
|
||
delete(doc.Components.Schemas, baseListRef)
|
||
return doc, nil
|
||
}
|
||
}
|
||
|
||
// -----------------------------------------------------------------------------
|
||
// OpenAPI **v2** (swagger) post-processor
|
||
// -----------------------------------------------------------------------------
|
||
func buildPostProcessV2(kindSchemas map[string]string) func(*spec.Swagger) (*spec.Swagger, error) {
|
||
|
||
return func(sw *spec.Swagger) (*spec.Swagger, error) {
|
||
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{}{
|
||
map[string]interface{}{
|
||
"group": "apps.cozystack.io", "version": "v1alpha1", "kind": kind,
|
||
},
|
||
},
|
||
}
|
||
|
||
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]
|
||
listCopy := deepCopySchema(&listSrc)
|
||
listCopy.Extensions = map[string]interface{}{
|
||
"x-kubernetes-group-version-kind": []interface{}{
|
||
map[string]interface{}{
|
||
"group": "apps.cozystack.io",
|
||
"version": "v1alpha1",
|
||
"kind": kind + "List",
|
||
},
|
||
},
|
||
}
|
||
if items := listCopy.Properties["items"]; items.Items != nil && items.Items.Schema != nil {
|
||
items.Items.Schema.Ref = spec.MustCreateRef("#/definitions/" + ref)
|
||
listCopy.Properties["items"] = items
|
||
}
|
||
defs[listName] = *listCopy
|
||
}
|
||
|
||
delete(defs, baseRef)
|
||
delete(defs, baseListRef)
|
||
return sw, nil
|
||
}
|
||
}
|