From 5a4c165020ecf9b50d75c185b31e54be57fa3b46 Mon Sep 17 00:00:00 2001 From: Andrei Kvapil Date: Mon, 25 Nov 2024 15:18:43 +0100 Subject: [PATCH] Fix OpenAPIv2 definitions for dynamic resources (#484) ## Summary by CodeRabbit - **New Features** - Enhanced OpenAPI schema handling for the Apps API server. - Introduced a method for deep copying schema structures to improve resource definition management. - **Bug Fixes** - Improved error handling during server configuration to ensure proper reporting of setup issues. - **Refactor** - Removed dynamic type registration for the `v1alpha1` API version to simplify server initialization. - **Chores** - Updated image tag for the CozyStack API to the latest version. --- packages/system/cozystack-api/values.yaml | 2 +- pkg/apis/apps/v1alpha1/register.go | 11 +-- pkg/apiserver/apiserver.go | 7 -- pkg/cmd/server/start.go | 87 +++++++++++++++++++++++ 4 files changed, 95 insertions(+), 12 deletions(-) diff --git a/packages/system/cozystack-api/values.yaml b/packages/system/cozystack-api/values.yaml index 8918555f6..403ee10ec 100644 --- a/packages/system/cozystack-api/values.yaml +++ b/packages/system/cozystack-api/values.yaml @@ -1,2 +1,2 @@ cozystackAPI: - image: ghcr.io/aenix-io/cozystack/cozystack-api:v0.18.0@sha256:d3f817ee20cc502b7c5deffa46a1ad94a6e1a74fa035dbeb65ef742e67fd1fe5 + image: ghcr.io/aenix-io/cozystack/cozystack-api:latest@sha256:96d277a34aff6d9847251da521ca86615af2aae370ec095d046112bc4044ddea diff --git a/pkg/apis/apps/v1alpha1/register.go b/pkg/apis/apps/v1alpha1/register.go index dc48a6128..cedebac79 100644 --- a/pkg/apis/apps/v1alpha1/register.go +++ b/pkg/apis/apps/v1alpha1/register.go @@ -17,17 +17,20 @@ limitations under the License. package v1alpha1 import ( - "log" - "github.com/aenix.io/cozystack/pkg/config" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" + "k8s.io/klog/v2" ) // GroupName holds the API group name. const GroupName = "apps.cozystack.io" +var ( + RegisteredGVKs []schema.GroupVersionKind +) + // SchemeGroupVersion is group version used to register these objects var SchemeGroupVersion = schema.GroupVersion{Group: GroupName, Version: "v1alpha1"} @@ -69,8 +72,8 @@ func RegisterDynamicTypes(scheme *runtime.Scheme, cfg *config.ResourceConfig) er scheme.AddKnownTypeWithName(gvk, &Application{}) scheme.AddKnownTypeWithName(gvk.GroupVersion().WithKind(kind+"List"), &ApplicationList{}) - log.Printf("Registered kind: %s\n", kind) - + klog.V(1).Infof("Registered kind: %s\n", kind) + RegisteredGVKs = append(RegisteredGVKs, gvk) } return nil diff --git a/pkg/apiserver/apiserver.go b/pkg/apiserver/apiserver.go index f5fbe4b75..aa8ec3494 100644 --- a/pkg/apiserver/apiserver.go +++ b/pkg/apiserver/apiserver.go @@ -31,7 +31,6 @@ import ( "github.com/aenix.io/cozystack/pkg/apis/apps" "github.com/aenix.io/cozystack/pkg/apis/apps/install" - appsv1alpha1 "github.com/aenix.io/cozystack/pkg/apis/apps/v1alpha1" "github.com/aenix.io/cozystack/pkg/config" appsregistry "github.com/aenix.io/cozystack/pkg/registry" applicationstorage "github.com/aenix.io/cozystack/pkg/registry/apps/application" @@ -112,12 +111,6 @@ func (c completedConfig) New() (*AppsServer, error) { apiGroupInfo := genericapiserver.NewDefaultAPIGroupInfo(apps.GroupName, Scheme, metav1.ParameterCodec, Codecs) - // Dynamically register types based on the configuration. - err = appsv1alpha1.RegisterDynamicTypes(Scheme, c.ResourceConfig) - if err != nil { - return nil, fmt.Errorf("failed to register dynamic types: %v", err) - } - // Create a dynamic client for HelmRelease using InClusterConfig. inClusterConfig, err := restclient.InClusterConfig() if err != nil { diff --git a/pkg/cmd/server/start.go b/pkg/cmd/server/start.go index 8cc910ee2..6593f17ad 100644 --- a/pkg/cmd/server/start.go +++ b/pkg/cmd/server/start.go @@ -18,6 +18,7 @@ package server import ( "context" + "encoding/json" "fmt" "io" "net" @@ -37,6 +38,8 @@ import ( utilversionpkg "k8s.io/apiserver/pkg/util/version" "k8s.io/component-base/featuregate" baseversion "k8s.io/component-base/version" + "k8s.io/klog/v2" + "k8s.io/kube-openapi/pkg/validation/spec" netutils "k8s.io/utils/net" ) @@ -156,6 +159,22 @@ func (o AppsServerOptions) Validate(args []string) error { return utilerrors.NewAggregate(allErrors) } +// DeepCopySchema делает глубокую копию структуры spec.Schema +func DeepCopySchema(schema *spec.Schema) (*spec.Schema, error) { + data, err := json.Marshal(schema) + if err != nil { + return nil, fmt.Errorf("failed to marshal schema: %w", err) + } + + var newSchema spec.Schema + err = json.Unmarshal(data, &newSchema) + if err != nil { + return nil, fmt.Errorf("failed to unmarshal schema: %w", err) + } + + return &newSchema, nil +} + // Config returns the configuration for the API server based on AppsServerOptions func (o *AppsServerOptions) Config() (*apiserver.Config, error) { // TODO: set the "real" external address @@ -165,6 +184,12 @@ func (o *AppsServerOptions) Config() (*apiserver.Config, error) { return nil, fmt.Errorf("error creating self-signed certificates: %v", err) } + // First, register the dynamic types + err := v1alpha1.RegisterDynamicTypes(apiserver.Scheme, o.ResourceConfig) + if err != nil { + return nil, fmt.Errorf("failed to register dynamic types: %v", err) + } + serverConfig := genericapiserver.NewRecommendedConfig(apiserver.Codecs) serverConfig.OpenAPIConfig = genericapiserver.DefaultOpenAPIConfig( @@ -173,6 +198,68 @@ func (o *AppsServerOptions) Config() (*apiserver.Config, error) { serverConfig.OpenAPIConfig.Info.Title = "Apps" serverConfig.OpenAPIConfig.Info.Version = "0.1" + serverConfig.OpenAPIConfig.PostProcessSpec = func(swagger *spec.Swagger) (*spec.Swagger, error) { + defs := swagger.Definitions + + // Check basic Application definition + appDef, exists := defs["com.github.aenix.io.cozystack.pkg.apis.apps.v1alpha1.Application"] + if !exists { + return swagger, fmt.Errorf("Application definition not found") + } + + // Check basic ApplicationList definition + listDef, exists := defs["com.github.aenix.io.cozystack.pkg.apis.apps.v1alpha1.ApplicationList"] + if !exists { + return swagger, fmt.Errorf("ApplicationList definition not found") + } + + for _, gvk := range v1alpha1.RegisteredGVKs { + resourceName := fmt.Sprintf("com.github.aenix.io.cozystack.pkg.apis.apps.v1alpha1.%s", gvk.Kind) + newDef, err := DeepCopySchema(&appDef) + if err != nil { + return nil, fmt.Errorf("failed to deepcopy schema for %s: %w", gvk.Kind, err) + } + + // Fix Extensions for resource + if newDef.Extensions == nil { + newDef.Extensions = map[string]interface{}{} + } + newDef.Extensions["x-kubernetes-group-version-kind"] = []map[string]interface{}{ + { + "group": gvk.Group, + "version": gvk.Version, + "kind": gvk.Kind, + }, + } + defs[resourceName] = *newDef + klog.V(6).Infof("PostProcessSpec: Added OpenAPI definition for %s\n", resourceName) + + // List resource + listResourceName := fmt.Sprintf("com.github.aenix.io.cozystack.pkg.apis.apps.v1alpha1.%sList", gvk.Kind) + newListDef, err := DeepCopySchema(&listDef) + if err != nil { + return nil, fmt.Errorf("failed to deepcopy schema for %sList: %w", gvk.Kind, err) + } + + // Fix Extensions for List resource + if newListDef.Extensions == nil { + newListDef.Extensions = map[string]interface{}{} + } + newListDef.Extensions["x-kubernetes-group-version-kind"] = []map[string]interface{}{ + { + "group": gvk.Group, + "version": gvk.Version, + "kind": fmt.Sprintf("%sList", gvk.Kind), + }, + } + defs[listResourceName] = *newListDef + klog.V(6).Infof("PostProcessSpec: Added OpenAPI definition for %s\n", listResourceName) + } + + swagger.Definitions = defs + return swagger, nil + } + serverConfig.OpenAPIV3Config = genericapiserver.DefaultOpenAPIV3Config( sampleopenapi.GetOpenAPIDefinitions, openapi.NewDefinitionNamer(apiserver.Scheme), )