From 820f56a6643b9b1c18e226763bd59c355bfc7ef3 Mon Sep 17 00:00:00 2001 From: Ruslan Gorbunov Date: Fri, 4 Sep 2026 11:48:52 +0300 Subject: [PATCH 1/2] fix: patch CRD conversion without overwriting fields Signed-off-by: Ruslan Gorbunov --- pkg/webhook/conversion/crd_client_config.go | 71 +++++++++++---------- pkg/webhook/conversion/manager.go | 4 +- 2 files changed, 40 insertions(+), 35 deletions(-) diff --git a/pkg/webhook/conversion/crd_client_config.go b/pkg/webhook/conversion/crd_client_config.go index af355486..31c9251c 100644 --- a/pkg/webhook/conversion/crd_client_config.go +++ b/pkg/webhook/conversion/crd_client_config.go @@ -2,11 +2,12 @@ package conversion import ( "context" + "encoding/json" "fmt" "time" extv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/types" klient "github.com/flant/kube-client/client" "github.com/flant/shell-operator/pkg" @@ -24,48 +25,52 @@ type CrdClientConfig struct { var SupportedConversionReviewVersions = []string{"v1", "v1beta1"} -func (c *CrdClientConfig) Update(ctx context.Context) error { +// PatchConversion points spec.conversion of the CRD at this operator's webhook +// server, leaving every other field of the CRD alone. +func (c *CrdClientConfig) PatchConversion(ctx context.Context) error { var ( retryTimeout = 15 * time.Second retryBudget = 60 // 60 times * 15 sec = 15 min client = c.KubeClient ) -tryToGetCRD: - crd, err := client.ApiExt().CustomResourceDefinitions().Get(ctx, c.CrdName, metav1.GetOptions{}) + conv, err := json.Marshal(&extv1.CustomResourceConversion{ + Strategy: extv1.WebhookConverter, + Webhook: &extv1.WebhookConversion{ + ClientConfig: &extv1.WebhookClientConfig{ + Service: &extv1.ServiceReference{ + Namespace: c.Namespace, + Name: c.ServiceName, + Path: &c.Path, + }, + CABundle: c.CABundle, + }, + ConversionReviewVersions: SupportedConversionReviewVersions, + }, + }) if err != nil { - if retryBudget > 0 { - retryBudget-- - time.Sleep(retryTimeout) - goto tryToGetCRD - } - - return fmt.Errorf("get CRD: %w", err) + return fmt.Errorf("marshal conversion: %w", err) } - if crd.Spec.Conversion == nil { - crd.Spec.Conversion = new(extv1.CustomResourceConversion) - } - crd.Spec.Conversion.Strategy = extv1.WebhookConverter + patch := []byte(`[{"op":"add","path":"/spec/conversion","value":` + string(conv) + `}]`) - if crd.Spec.Conversion.Webhook == nil { - crd.Spec.Conversion.Webhook = new(extv1.WebhookConversion) - } - crd.Spec.Conversion.Webhook.ClientConfig = &extv1.WebhookClientConfig{ - URL: nil, - Service: &extv1.ServiceReference{ - Namespace: c.Namespace, - Name: c.ServiceName, - Path: &c.Path, - }, - CABundle: c.CABundle, - } - crd.Spec.Conversion.Webhook.ConversionReviewVersions = SupportedConversionReviewVersions + // The CRD is often absent when a hook registers its conversion bindings, so the + // patch is retried on the budget the Get used to hold. + for { + _, err = client.ApiExt().CustomResourceDefinitions().Patch(ctx, c.CrdName, types.JSONPatchType, patch, pkg.DefaultPatchOptions()) + if err == nil { + return nil + } - _, err = client.ApiExt().CustomResourceDefinitions().Update(ctx, crd, pkg.DefaultUpdateOptions()) - if err != nil { - return fmt.Errorf("update CRD: %w", err) - } + if retryBudget == 0 { + return fmt.Errorf("patch CRD conversion: %w", err) + } + retryBudget-- - return nil + select { + case <-ctx.Done(): + return fmt.Errorf("patch CRD conversion: %w", ctx.Err()) + case <-time.After(retryTimeout): + } + } } diff --git a/pkg/webhook/conversion/manager.go b/pkg/webhook/conversion/manager.go index f5b5b8d2..7ec91e79 100644 --- a/pkg/webhook/conversion/manager.go +++ b/pkg/webhook/conversion/manager.go @@ -94,9 +94,9 @@ func (m *WebhookManager) Start() error { } for _, clientCfg := range m.ClientConfigs { - err = clientCfg.Update(ctx) + err = clientCfg.PatchConversion(ctx) if err != nil { - return fmt.Errorf("update CRD: %w", err) + return fmt.Errorf("patch CRD conversion: %w", err) } } From f8186f7809e4a880fe39efe6f0bcb8cf51b84f30 Mon Sep 17 00:00:00 2001 From: Ruslan Gorbunov Date: Fri, 4 Sep 2026 14:57:25 +0300 Subject: [PATCH 2/2] fix: use current kube client for conversion patch Signed-off-by: Ruslan Gorbunov --- pkg/webhook/conversion/crd_client_config.go | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/pkg/webhook/conversion/crd_client_config.go b/pkg/webhook/conversion/crd_client_config.go index 31c9251c..ef5bd27c 100644 --- a/pkg/webhook/conversion/crd_client_config.go +++ b/pkg/webhook/conversion/crd_client_config.go @@ -31,7 +31,6 @@ func (c *CrdClientConfig) PatchConversion(ctx context.Context) error { var ( retryTimeout = 15 * time.Second retryBudget = 60 // 60 times * 15 sec = 15 min - client = c.KubeClient ) conv, err := json.Marshal(&extv1.CustomResourceConversion{ @@ -57,7 +56,7 @@ func (c *CrdClientConfig) PatchConversion(ctx context.Context) error { // The CRD is often absent when a hook registers its conversion bindings, so the // patch is retried on the budget the Get used to hold. for { - _, err = client.ApiExt().CustomResourceDefinitions().Patch(ctx, c.CrdName, types.JSONPatchType, patch, pkg.DefaultPatchOptions()) + _, err = c.KubeClient.ApiExt().CustomResourceDefinitions().Patch(ctx, c.CrdName, types.JSONPatchType, patch, pkg.DefaultPatchOptions()) if err == nil { return nil }