Repository: yokecd/yoke
Pull request #58 (2025-01-10 13:58:16)

atc: refactor flight status and ensure that flight status carry over during crd conversion webhooks


Patch:
diff --git a/cmd/atc/handler.go b/cmd/atc/handler.go
index 2cff6e6..d406b7a 100644
--- a/cmd/atc/handler.go
+++ b/cmd/atc/handler.go
@@ -69,6 +69,14 @@ func Handler(client *k8s.Client, cache *wasm.ModuleCache, logger *slog.Logger) h
 			desiredAPIVersion = review.Request.DesiredAPIVersion
 		}
 
+		originals := make([]map[string]any, len(review.Request.Objects))
+		for i, obj := range review.Request.Objects {
+			if err := json.Unmarshal(obj.Raw, &originals[i]); err != nil {
+				http.Error(w, fmt.Sprintf("could not Unmarshal request object: %v", err), http.StatusBadRequest)
+				return
+			}
+		}
+
 		resp, err := wasi.Execute(ctx, wasi.ExecParams{
 			CompiledModule: converter.CompiledModule,
 			Stdin:          bytes.NewReader(data),
@@ -80,21 +88,41 @@ func Handler(client *k8s.Client, cache *wasm.ModuleCache, logger *slog.Logger) h
 			return
 		}
 
-		if err := json.Unmarshal(resp, &review); err == nil {
-			addRequestAttrs(
-				r.Context(),
-				slog.Group(
-					"converter",
-					"status", review.Response.Result.Status,
-					"reason", review.Response.Result.Reason,
-					"count", len(review.Response.ConvertedObjects),
-					"uid", review.Response.UID,
-					"DesiredAPIVersion", desiredAPIVersion,
-				),
-			)
+		if err := json.Unmarshal(resp, &review); err != nil {
+			http.Error(w, fmt.Sprintf("failed to parse review response: %v", err), http.StatusInternalServerError)
+			return
 		}
 
-		if _, err := w.Write(resp); err != nil {
+		for i, converted := range review.Response.ConvertedObjects {
+			originalStatus, _, err := unstructured.NestedMap(originals[i], "status")
+			if err != nil {
+				continue
+			}
+
+			var raw map[string]any
+			if err := json.Unmarshal(converted.Raw, &raw); err != nil {
+				continue
+			}
+
+			unstructured.SetNestedField(raw, originalStatus, "status")
+
+			data, err := json.Marshal(raw)
+			if err != nil {
+				continue
+			}
+			converted.Raw = data
+		}
+
+		addRequestAttrs(r.Context(), slog.Group(
+			"converter",
+			"status", review.Response.Result.Status,
+			"reason", review.Response.Result.Reason,
+			"count", len(review.Response.ConvertedObjects),
+			"uid", review.Response.UID,
+			"DesiredAPIVersion", desiredAPIVersion,
+		))
+
+		if err := json.NewEncoder(w).Encode(review); err != nil {
 			logger.Error("unexpected: failed to write response to connection", "error", err)
 		}
 	})
diff --git a/cmd/yoke/cmd_atc.go b/cmd/yoke/cmd_atc.go
index 772bead..b3aa4c3 100644
--- a/cmd/yoke/cmd_atc.go
+++ b/cmd/yoke/cmd_atc.go
@@ -2,6 +2,7 @@ package main
 
 import (
 	"context"
+	"flag"
 	"fmt"
 	"runtime"
 	"sync"
@@ -17,17 +18,41 @@ import (
 	"k8s.io/client-go/dynamic"
 
 	"github.com/yokecd/yoke/internal/atc/tower"
-	"github.com/yokecd/yoke/internal/home"
 	"github.com/yokecd/yoke/internal/k8s"
 	"github.com/yokecd/yoke/pkg/apis/airway/v1alpha1"
 )
 
-func ATC(ctx context.Context) error {
-	client, err := k8s.NewClientFromKubeConfig(home.Kubeconfig)
+type ATCParams struct {
+	GlobalSettings
+	Debug string
+}
+
+func GetAtcParams(settings GlobalSettings, args []string) ATCParams {
+	flagset := flag.NewFlagSet("atc", flag.ExitOnError)
+
+	params := ATCParams{GlobalSettings: settings}
+
+	RegisterGlobalFlags(flagset, &params.GlobalSettings)
+
+	flagset.StringVar(&params.Debug, "debug-file", "", "debug file")
+
+	flagset.Parse(args)
+
+	return params
+}
+
+func ATC(ctx context.Context, params ATCParams) error {
+	client, err := k8s.NewClientFromKubeConfig(params.KubeConfigPath)
 	if err != nil {
 		return fmt.Errorf("failed to initialize kube client: %w", err)
 	}
 
+	if params.Debug != "" {
+		if err := tower.SetupDebugFile(params.Debug); err != nil {
+			return fmt.Errorf("failed to setup debug file: %w", err)
+		}
+	}
+
 	app := tea.NewProgram(
 		tower.ATCDashboard{
 			Content: tower.MakeAirwayListView(tea.WindowSizeMsg{}),
diff --git a/cmd/yoke/main.go b/cmd/yoke/main.go
index 61d9868..d25ba85 100644
--- a/cmd/yoke/main.go
+++ b/cmd/yoke/main.go
@@ -61,7 +61,7 @@ func run() error {
 
 	switch cmd := flag.Arg(0); cmd {
 	case "atc":
-		return ATC(ctx)
+		return ATC(ctx, GetAtcParams(settings, subcmdArgs))
 	case "takeoff", "up", "apply":
 		{
 			var source io.Reader
diff --git a/internal/atc/atc.go b/internal/atc/atc.go
index 741aacb..cb64392 100644
--- a/internal/atc/atc.go
+++ b/internal/atc/atc.go
@@ -33,6 +33,7 @@ import (
 	"github.com/yokecd/yoke/internal/k8s/ctrl"
 	"github.com/yokecd/yoke/internal/wasi"
 	"github.com/yokecd/yoke/pkg/apis/airway/v1alpha1"
+	"github.com/yokecd/yoke/pkg/flight"
 	"github.com/yokecd/yoke/pkg/openapi"
 	"github.com/yokecd/yoke/pkg/yoke"
 )
@@ -99,7 +100,7 @@ func (atc atc) Reconcile(ctx context.Context, event ctrl.Event) (result ctrl.Res
 
 		_ = unstructured.SetNestedMap(
 			airway.Object,
-			internal.MustUnstructuredObject(v1alpha1.AirwayStatus{Status: status, Msg: fmt.Sprintf("%v", msg)}),
+			internal.MustUnstructuredObject(flight.Status{Status: status, Msg: fmt.Sprintf("%v", msg)}),
 			"status",
 		)
 
@@ -143,6 +144,8 @@ func (atc atc) Reconcile(ctx context.Context, event ctrl.Event) (result ctrl.Res
 	modules := atc.moduleCache.Get(typedAirway.Name)
 
 	if typedAirway.DeletionTimestamp != nil {
+		airwayStatus("Terminating", "cleaning up resources")
+
 		if idx := slices.Index(typedAirway.Finalizers, cleanupAirwayFinalizer); idx > -1 {
 			if err := webhookIntf.Delete(ctx, typedAirway.CRGroupResource().String(), metav1.DeleteOptions{}); err != nil && !kerrors.IsNotFound(err) {
 				return ctrl.Result{}, fmt.Errorf("failed to remove admission validation webhook: %w", err)
@@ -239,7 +242,7 @@ func (atc atc) Reconcile(ctx context.Context, event ctrl.Event) (result ctrl.Res
 				Properties: apiextv1.JSONSchemaDefinitions{},
 			}
 		}
-		version.Schema.OpenAPIV3Schema.Properties["status"] = *openapi.SchemaFrom(reflect.TypeFor[FlightStatus]())
+		version.Schema.OpenAPIV3Schema.Properties["status"] = *openapi.SchemaFrom(reflect.TypeFor[flight.Status]())
 	}
 
 	if typedAirway.Spec.WasmURLs.Converter != "" {
@@ -425,7 +428,7 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
 			return ctrl.Client(ctx).Dynamic.Resource(mapping.Resource)
 		}()
 
-		flight, err := resourceIntf.Get(ctx, event.Name, metav1.GetOptions{})
+		resource, err := resourceIntf.Get(ctx, event.Name, metav1.GetOptions{})
 		if err != nil {
 			if kerrors.IsNotFound(err) {
 				ctrl.Logger(ctx).Info("resource not found")
@@ -434,9 +437,9 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
 			return ctrl.Result{}, fmt.Errorf("failed to get resource: %w", err)
 		}
 
-		if flight.GetNamespace() == "" && mapping.Scope == meta.RESTScopeNamespace {
-			flight.SetNamespace("default")
-			if _, err := resourceIntf.Update(ctx, flight, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
+		if resource.GetNamespace() == "" && mapping.Scope == meta.RESTScopeNamespace {
+			resource.SetNamespace("default")
+			if _, err := resourceIntf.Update(ctx, resource, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
 				return ctrl.Result{}, fmt.Errorf("failed to set default namespace on flight: %w", err)
 			}
 
@@ -445,25 +448,25 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
 
 		flightStatus := func(status string, msg any) {
 			var err error
-			flight, err = resourceIntf.Get(ctx, flight.GetName(), metav1.GetOptions{})
+			resource, err = resourceIntf.Get(ctx, resource.GetName(), metav1.GetOptions{})
 			if err != nil {
 				ctrl.Logger(ctx).Error("failed to update flight status", "error", fmt.Errorf("failed to get flight: %v", err))
 				return
 			}
 
 			_ = unstructured.SetNestedMap(
-				flight.Object,
-				internal.MustUnstructuredObject(FlightStatus{Status: status, Msg: fmt.Sprintf("%v", msg)}),
+				resource.Object,
+				internal.MustUnstructuredObject(flight.Status{Status: status, Msg: fmt.Sprintf("%v", msg)}),
 				"status",
 			)
 
-			updated, err := resourceIntf.UpdateStatus(ctx, flight.DeepCopy(), metav1.UpdateOptions{FieldManager: fieldManager})
+			updated, err := resourceIntf.UpdateStatus(ctx, resource.DeepCopy(), metav1.UpdateOptions{FieldManager: fieldManager})
 			if err != nil {
 				ctrl.Logger(ctx).Error("failed to update flight status", "error", err)
 				return
 			}
 
-			flight = updated
+			resource = updated
 		}
 
 		defer func() {
@@ -472,28 +475,28 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
 			}
 		}()
 
-		if finalizers := flight.GetFinalizers(); flight.GetDeletionTimestamp() == nil && !slices.Contains(finalizers, cleanupFinalizer) {
-			flight.SetFinalizers(append(finalizers, cleanupFinalizer))
-			if _, err := resourceIntf.Update(ctx, flight, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
+		if finalizers := resource.GetFinalizers(); resource.GetDeletionTimestamp() == nil && !slices.Contains(finalizers, cleanupFinalizer) {
+			resource.SetFinalizers(append(finalizers, cleanupFinalizer))
+			if _, err := resourceIntf.Update(ctx, resource, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
 				return ctrl.Result{}, fmt.Errorf("failed to set cleanup finalizer: %w", err)
 			}
 			return ctrl.Result{}, nil
 		}
 
-		if !flight.GetDeletionTimestamp().IsZero() {
+		if !resource.GetDeletionTimestamp().IsZero() {
 			flightStatus("Terminating", "Mayday: Flight is being removed")
 
-			if err := yoke.FromK8Client(ctrl.Client(ctx)).Mayday(ctx, ReleaseName(flight), event.Namespace); err != nil {
+			if err := yoke.FromK8Client(ctrl.Client(ctx)).Mayday(ctx, ReleaseName(resource), event.Namespace); err != nil {
 				if !internal.IsWarning(err) {
 					return ctrl.Result{}, fmt.Errorf("failed to run atc cleanup: %w", err)
 				}
 				ctrl.Logger(ctx).Warn("mayday succeeded despite a warning", "warning", err)
 			}
 
-			finalizers := flight.GetFinalizers()
+			finalizers := resource.GetFinalizers()
 			if idx := slices.Index(finalizers, cleanupFinalizer); idx != -1 {
-				flight.SetFinalizers(slices.Delete(finalizers, idx, idx+1))
-				if _, err := resourceIntf.Update(ctx, flight, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
+				resource.SetFinalizers(slices.Delete(finalizers, idx, idx+1))
+				if _, err := resourceIntf.Update(ctx, resource, metav1.UpdateOptions{FieldManager: fieldManager}); err != nil {
 					return ctrl.Result{}, fmt.Errorf("failed to remove finalizer: %w", err)
 				}
 			}
@@ -501,7 +504,7 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
 			return ctrl.Result{}, nil
 		}
 
-		object, _, err := unstructured.NestedFieldNoCopy(flight.Object, params.ObjectPath...)
+		object, _, err := unstructured.NestedFieldNoCopy(resource.Object, params.ObjectPath...)
 		if err != nil {
 			return ctrl.Result{}, fmt.Errorf("failed to get object path from: %q: %v", strings.Join(params.ObjectPath, ","), err)
 		}
@@ -517,7 +520,7 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
 		commander := yoke.FromK8Client(ctrl.Client(ctx))
 
 		takeoffParams := yoke.TakeoffParams{
-			Release: ReleaseName(flight),
+			Release: ReleaseName(resource),
 			Flight: yoke.FlightParams{
 				WasmModule:          params.FlightMod.CompiledModule,
 				Input:               bytes.NewReader(data),
@@ -527,10 +530,10 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
 			CreateCRDs: params.CreateCrds,
 			OwnerReferences: []metav1.OwnerReference{
 				{
-					APIVersion: flight.GetAPIVersion(),
-					Kind:       flight.GetKind(),
-					Name:       flight.GetName(),
-					UID:        flight.GetUID(),
+					APIVersion: resource.GetAPIVersion(),
+					Kind:       resource.GetKind(),
+					Name:       resource.GetName(),
+					UID:        resource.GetUID(),
 				},
 			},
 		}
@@ -561,11 +564,6 @@ func (atc atc) FlightReconciler(params FlightReconcilerParams) ctrl.HandleFunc {
 	}
 }
 
-type FlightStatus struct {
-	Status string `json:"status"`
-	Msg    string `json:"msg"`
-}
-
 func ReleaseName(resource *unstructured.Unstructured) string {
 	gvk := resource.GroupVersionKind()
 	elems := []string{
diff --git a/internal/atc/tower/dashboard.go b/internal/atc/tower/dashboard.go
index b71b47f..0e73c6a 100644
--- a/internal/atc/tower/dashboard.go
+++ b/internal/atc/tower/dashboard.go
@@ -3,6 +3,7 @@ package tower
 import (
 	"fmt"
 	"os"
+	"time"
 
 	tea "github.com/charmbracelet/bubbletea"
 	"github.com/charmbracelet/lipgloss"
@@ -18,12 +19,20 @@ var banner = cyanSyle.Render(`     _   _      _____
 
 var debugFile *os.File
 
-func init() {
-	_ = os.Remove("./debug.txt")
-	debugFile, _ = os.OpenFile("./debug.txt", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0o644)
-	if _, err := debugFile.WriteString("DEBUG.txt\n\n"); err != nil {
-		panic(err)
+func debugf(format string, args ...any) {
+	if debugFile == nil {
+		return
 	}
+	_, _ = fmt.Fprintf(debugFile, format+"\n", args...)
+}
+
+func SetupDebugFile(dst string) (err error) {
+	debugFile, err = os.OpenFile(dst, os.O_CREATE|os.O_APPEND|os.O_RDWR, 0o644)
+	if err != nil {
+		return err
+	}
+	_, err = fmt.Fprintf(debugFile, "debug session start: %s\n\n", time.Now().Format(time.RFC3339Nano))
+	return err
 }
 
 type Commands struct {
@@ -43,11 +52,14 @@ func (dashboard ATCDashboard) Init() tea.Cmd {
 }
 
 func (dashboard ATCDashboard) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
-	fmt.Fprintf(debugFile, "%#v\n\n", msg)
-	debugFile.Sync()
+	debugf("%v", func() any {
+		if value, ok := msg.(fmt.Stringer); ok {
+			return value.String()
+		}
+		return msg
+	}())
 
 	switch msg := msg.(type) {
-
 	case tea.KeyMsg:
 		switch msg.Type {
 		case tea.KeyCtrlC:
@@ -55,6 +67,7 @@ func (dashboard ATCDashboard) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
 		case tea.KeyCtrlZ:
 			return dashboard, tea.Suspend
 		}
+
 	case ExecMsg:
 		return dashboard, msg(dashboard.Commands)
 	}
diff --git a/pkg/apis/airway/v1alpha1/airway.go b/pkg/apis/airway/v1alpha1/airway.go
index 397253c..b68c522 100644
--- a/pkg/apis/airway/v1alpha1/airway.go
+++ b/pkg/apis/airway/v1alpha1/airway.go
@@ -7,6 +7,7 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime/schema"
 
+	"github.com/yokecd/yoke/pkg/flight"
 	"github.com/yokecd/yoke/pkg/openapi"
 )
 
@@ -18,8 +19,8 @@ const (
 type Airway struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
-	Spec              AirwaySpec   `json:"spec"`
-	Status            AirwayStatus `json:"status,omitempty"`
+	Spec              AirwaySpec    `json:"spec"`
+	Status            flight.Status `json:"status,omitempty"`
 }
 
 type AirwaySpec struct {
@@ -35,11 +36,6 @@ type WasmURLs struct {
 	Converter string `json:"converter,omitempty"`
 }
 
-type AirwayStatus struct {
-	Status string `json:"status"`
-	Msg    string `json:"msg"`
-}
-
 func (airway Airway) MarshalJSON() ([]byte, error) {
 	airway.Kind = KindAirway
 	airway.APIVersion = APIVersion
diff --git a/pkg/flight/flight.go b/pkg/flight/flight.go
index d3aaf58..49aa88c 100644
--- a/pkg/flight/flight.go
+++ b/pkg/flight/flight.go
@@ -21,3 +21,8 @@ func Release() string {
 func Namespace() string {
 	return cmp.Or(os.Getenv("YOKE_NAMESPACE"), os.Getenv("NAMESPACE"))
 }
+
+type Status struct {
+	Status string `json:"status,omitempty"`
+	Msg    string `json:"msg,omitempty"`
+}


Test patch:
diff --git a/pkg/openapi/schema_test.go b/pkg/openapi/schema_test.go
index 9e4cf3b..a462048 100644
--- a/pkg/openapi/schema_test.go
+++ b/pkg/openapi/schema_test.go
@@ -1,12 +1,17 @@
-package openapi
+package openapi_test
 
 import (
+	"encoding/json"
 	"reflect"
 	"testing"
 
 	"github.com/stretchr/testify/require"
 
+	"github.com/yokecd/yoke/pkg/apis/airway/v1alpha1"
+	"github.com/yokecd/yoke/pkg/openapi"
+
 	apiext "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/utils/ptr"
 )
 
 func TestGenerateSchema(t *testing.T) {
@@ -31,11 +36,11 @@ func TestGenerateSchema(t *testing.T) {
 			Properties: apiext.JSONSchemaDefinitions{
 				"name": {
 					Type:      "string",
-					MinLength: ptr[int64](3),
+					MinLength: ptr.To[int64](3),
 				},
 				"age": {
 					Type:    "integer",
-					Minimum: ptr[float64](18),
+					Minimum: ptr.To[float64](18),
 				},
 				"active": {
 					Type: "boolean",
@@ -69,6 +74,564 @@ func TestGenerateSchema(t *testing.T) {
 			},
 			Required: []string{"name", "age", "active", "choice", "rule"},
 		},
-		SchemaFrom(reflect.TypeOf(S{})),
+		openapi.SchemaFrom(reflect.TypeOf(S{})),
 	)
 }
+
+func TestAirwaySchema(t *testing.T) {
+	schema := openapi.SchemaFrom(reflect.TypeFor[v1alpha1.Airway]())
+
+	data, err := json.MarshalIndent(schema, "", "  ")
+	require.NoError(t, err)
+
+	require.JSONEq(t, string(data), `{
+  "type": "object",
+  "required": [
+    "spec"
+  ],
+  "properties": {
+    "spec": {
+      "type": "object",
+      "required": [
+        "wasmUrls",
+        "template"
+      ],
+      "properties": {
+        "createCrds": {
+          "type": "boolean"
+        },
+        "fixDriftInterval": {
+          "type": "string"
+        },
+        "objectPath": {
+          "type": "array",
+          "items": {
+            "type": "string"
+          }
+        },
+        "template": {
+          "type": "object",
+          "required": [
+            "group",
+            "names",
+            "scope",
+            "versions"
+          ],
+          "properties": {
+            "conversion": {
+              "type": "object",
+              "required": [
+                "strategy"
+              ],
+              "properties": {
+                "strategy": {
+                  "type": "string"
+                },
+                "webhook": {
+                  "type": "object",
+                  "required": [
+                    "conversionReviewVersions"
+                  ],
+                  "properties": {
+                    "clientConfig": {
+                      "type": "object",
+                      "properties": {
+                        "caBundle": {
+                          "type": "array",
+                          "items": {
+                            "type": "integer"
+                          }
+                        },
+                        "service": {
+                          "type": "object",
+                          "required": [
+                            "namespace",
+                            "name"
+                          ],
+                          "properties": {
+                            "name": {
+                              "type": "string"
+                            },
+                            "namespace": {
+                              "type": "string"
+                            },
+                            "path": {
+                              "type": "string"
+                            },
+                            "port": {
+                              "type": "integer"
+                            }
+                          }
+                        },
+                        "url": {
+                          "type": "string"
+                        }
+                      }
+                    },
+                    "conversionReviewVersions": {
+                      "type": "array",
+                      "items": {
+                        "type": "string"
+                      }
+                    }
+                  }
+                }
+              }
+            },
+            "group": {
+              "type": "string"
+            },
+            "names": {
+              "type": "object",
+              "required": [
+                "plural",
+                "kind"
+              ],
+              "properties": {
+                "categories": {
+                  "type": "array",
+                  "items": {
+                    "type": "string"
+                  }
+                },
+                "kind": {
+                  "type": "string"
+                },
+                "listKind": {
+                  "type": "string"
+                },
+                "plural": {
+                  "type": "string"
+                },
+                "shortNames": {
+                  "type": "array",
+                  "items": {
+                    "type": "string"
+                  }
+                },
+                "singular": {
+                  "type": "string"
+                }
+              }
+            },
+            "preserveUnknownFields": {
+              "type": "boolean"
+            },
+            "scope": {
+              "type": "string"
+            },
+            "versions": {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "required": [
+                  "name",
+                  "served",
+                  "storage"
+                ],
+                "properties": {
+                  "additionalPrinterColumns": {
+                    "type": "array",
+                    "items": {
+                      "type": "object",
+                      "required": [
+                        "name",
+                        "type",
+                        "jsonPath"
+                      ],
+                      "properties": {
+                        "description": {
+                          "type": "string"
+                        },
+                        "format": {
+                          "type": "string"
+                        },
+                        "jsonPath": {
+                          "type": "string"
+                        },
+                        "name": {
+                          "type": "string"
+                        },
+                        "priority": {
+                          "type": "integer"
+                        },
+                        "type": {
+                          "type": "string"
+                        }
+                      }
+                    }
+                  },
+                  "deprecated": {
+                    "type": "boolean"
+                  },
+                  "deprecationWarning": {
+                    "type": "string"
+                  },
+                  "name": {
+                    "type": "string"
+                  },
+                  "schema": {
+                    "type": "object",
+                    "properties": {
+                      "openAPIV3Schema": {
+                        "type": "object",
+                        "properties": {
+                          "$ref": {
+                            "type": "string"
+                          },
+                          "$schema": {
+                            "type": "string"
+                          },
+                          "additionalItems": {
+                            "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaPropsOrBool",
+                            "type": "object",
+                            "x-kubernetes-preserve-unknown-fields": true
+                          },
+                          "additionalProperties": {
+                            "type": "object",
+                            "required": [
+                              "Allows"
+                            ],
+                            "properties": {
+                              "Allows": {
+                                "type": "boolean"
+                              },
+                              "Schema": {
+                                "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                                "type": "object",
+                                "x-kubernetes-preserve-unknown-fields": true
+                              }
+                            }
+                          },
+                          "allOf": {
+                            "type": "array",
+                            "items": {
+                              "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                              "type": "object",
+                              "x-kubernetes-preserve-unknown-fields": true
+                            }
+                          },
+                          "anyOf": {
+                            "type": "array",
+                            "items": {
+                              "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                              "type": "object",
+                              "x-kubernetes-preserve-unknown-fields": true
+                            }
+                          },
+                          "default": {
+                            "type": "object",
+                            "required": [
+                              "-"
+                            ],
+                            "properties": {
+                              "-": {
+                                "type": "array",
+                                "items": {
+                                  "type": "integer"
+                                }
+                              }
+                            }
+                          },
+                          "definitions": {
+                            "type": "object",
+                            "additionalProperties": {
+                              "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                              "type": "object",
+                              "x-kubernetes-preserve-unknown-fields": true
+                            }
+                          },
+                          "dependencies": {
+                            "type": "object",
+                            "additionalProperties": {
+                              "type": "object",
+                              "required": [
+                                "Property"
+                              ],
+                              "properties": {
+                                "Property": {
+                                  "type": "array",
+                                  "items": {
+                                    "type": "string"
+                                  }
+                                },
+                                "Schema": {
+                                  "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                                  "type": "object",
+                                  "x-kubernetes-preserve-unknown-fields": true
+                                }
+                              }
+                            }
+                          },
+                          "description": {
+                            "type": "string"
+                          },
+                          "enum": {
+                            "type": "array",
+                            "items": {
+                              "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSON",
+                              "type": "object",
+                              "x-kubernetes-preserve-unknown-fields": true
+                            }
+                          },
+                          "example": {
+                            "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSON",
+                            "type": "object",
+                            "x-kubernetes-preserve-unknown-fields": true
+                          },
+                          "exclusiveMaximum": {
+                            "type": "boolean"
+                          },
+                          "exclusiveMinimum": {
+                            "type": "boolean"
+                          },
+                          "externalDocs": {
+                            "type": "object",
+                            "properties": {
+                              "description": {
+                                "type": "string"
+                              },
+                              "url": {
+                                "type": "string"
+                              }
+                            }
+                          },
+                          "format": {
+                            "type": "string"
+                          },
+                          "id": {
+                            "type": "string"
+                          },
+                          "items": {
+                            "type": "object",
+                            "required": [
+                              "JSONSchemas"
+                            ],
+                            "properties": {
+                              "JSONSchemas": {
+                                "type": "array",
+                                "items": {
+                                  "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                                  "type": "object",
+                                  "x-kubernetes-preserve-unknown-fields": true
+                                }
+                              },
+                              "Schema": {
+                                "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                                "type": "object",
+                                "x-kubernetes-preserve-unknown-fields": true
+                              }
+                            }
+                          },
+                          "maxItems": {
+                            "type": "integer"
+                          },
+                          "maxLength": {
+                            "type": "integer"
+                          },
+                          "maxProperties": {
+                            "type": "integer"
+                          },
+                          "maximum": {
+                            "type": "number"
+                          },
+                          "minItems": {
+                            "type": "integer"
+                          },
+                          "minLength": {
+                            "type": "integer"
+                          },
+                          "minProperties": {
+                            "type": "integer"
+                          },
+                          "minimum": {
+                            "type": "number"
+                          },
+                          "multipleOf": {
+                            "type": "number"
+                          },
+                          "not": {
+                            "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                            "type": "object",
+                            "x-kubernetes-preserve-unknown-fields": true
+                          },
+                          "nullable": {
+                            "type": "boolean"
+                          },
+                          "oneOf": {
+                            "type": "array",
+                            "items": {
+                              "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                              "type": "object",
+                              "x-kubernetes-preserve-unknown-fields": true
+                            }
+                          },
+                          "pattern": {
+                            "type": "string"
+                          },
+                          "patternProperties": {
+                            "type": "object",
+                            "additionalProperties": {
+                              "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                              "type": "object",
+                              "x-kubernetes-preserve-unknown-fields": true
+                            }
+                          },
+                          "properties": {
+                            "type": "object",
+                            "additionalProperties": {
+                              "description": "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1:JSONSchemaProps",
+                              "type": "object",
+                              "x-kubernetes-preserve-unknown-fields": true
+                            }
+                          },
+                          "required": {
+                            "type": "array",
+                            "items": {
+                              "type": "string"
+                            }
+                          },
+                          "title": {
+                            "type": "string"
+                          },
+                          "type": {
+                            "type": "string"
+                          },
+                          "uniqueItems": {
+                            "type": "boolean"
+                          },
+                          "x-kubernetes-embedded-resource": {
+                            "type": "boolean"
+                          },
+                          "x-kubernetes-int-or-string": {
+                            "type": "boolean"
+                          },
+                          "x-kubernetes-list-map-keys": {
+                            "type": "array",
+                            "items": {
+                              "type": "string"
+                            }
+                          },
+                          "x-kubernetes-list-type": {
+                            "type": "string"
+                          },
+                          "x-kubernetes-map-type": {
+                            "type": "string"
+                          },
+                          "x-kubernetes-preserve-unknown-fields": {
+                            "type": "boolean"
+                          },
+                          "x-kubernetes-validations": {
+                            "type": "array",
+                            "items": {
+                              "type": "object",
+                              "required": [
+                                "rule"
+                              ],
+                              "properties": {
+                                "fieldPath": {
+                                  "type": "string"
+                                },
+                                "message": {
+                                  "type": "string"
+                                },
+                                "messageExpression": {
+                                  "type": "string"
+                                },
+                                "optionalOldSelf": {
+                                  "type": "boolean"
+                                },
+                                "reason": {
+                                  "type": "string"
+                                },
+                                "rule": {
+                                  "type": "string"
+                                }
+                              }
+                            }
+                          }
+                        }
+                      }
+                    }
+                  },
+                  "selectableFields": {
+                    "type": "array",
+                    "items": {
+                      "type": "object",
+                      "required": [
+                        "jsonPath"
+                      ],
+                      "properties": {
+                        "jsonPath": {
+                          "type": "string"
+                        }
+                      }
+                    }
+                  },
+                  "served": {
+                    "type": "boolean"
+                  },
+                  "storage": {
+                    "type": "boolean"
+                  },
+                  "subresources": {
+                    "type": "object",
+                    "properties": {
+                      "scale": {
+                        "type": "object",
+                        "required": [
+                          "specReplicasPath",
+                          "statusReplicasPath"
+                        ],
+                        "properties": {
+                          "labelSelectorPath": {
+                            "type": "string"
+                          },
+                          "specReplicasPath": {
+                            "type": "string"
+                          },
+                          "statusReplicasPath": {
+                            "type": "string"
+                          }
+                        }
+                      },
+                      "status": {
+                        "type": "object"
+                      }
+                    }
+                  }
+                }
+              }
+            }
+          }
+        },
+        "wasmUrls": {
+          "type": "object",
+          "required": [
+            "flight"
+          ],
+          "properties": {
+            "converter": {
+              "type": "string"
+            },
+            "flight": {
+              "type": "string"
+            }
+          }
+        }
+      }
+    },
+    "status": {
+      "type": "object",
+      "properties": {
+        "msg": {
+          "type": "string"
+        },
+        "status": {
+          "type": "string"
+        }
+      }
+    }
+  }
+}`)
+}