diff --git a/pkg/webhook/conversion/crd_client_config.go b/pkg/webhook/conversion/crd_client_config.go index af355486..ef5bd27c 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,51 @@ 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 = c.KubeClient.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) } }