From cecc5861af1e8eb7aab966b11de835fa4be3b0c6 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Wed, 18 Feb 2026 00:13:25 +0300 Subject: [PATCH] fix(operator): validate CRD apiVersion, respect SIGTERM during install - Check both apiVersion and kind when validating embedded CRD manifests to prevent applying objects with wrong API group - Move ctrl.SetupSignalHandler() before install phases so CRD and Flux installs respect SIGTERM instead of blocking for up to 2 minutes - Replace custom contains/searchString helpers with strings.Contains Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- cmd/cozystack-operator/main.go | 10 ++++++---- internal/crdinstall/install.go | 6 +++--- internal/manifestutil/crd_test.go | 15 ++------------- 3 files changed, 11 insertions(+), 20 deletions(-) diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index f365a076..a0efdd84 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -180,10 +180,13 @@ func main() { os.Exit(1) } + // Set up signal handler early so install phases respect SIGTERM + mgrCtx := ctrl.SetupSignalHandler() + // Install Cozystack CRDs before starting reconcile loop if installCRDs { setupLog.Info("Installing Cozystack CRDs before starting reconcile loop") - installCtx, installCancel := context.WithTimeout(context.Background(), 2*time.Minute) + installCtx, installCancel := context.WithTimeout(mgrCtx, 2*time.Minute) defer installCancel() if err := crdinstall.Install(installCtx, directClient, crdinstall.WriteEmbeddedManifests); err != nil { @@ -196,7 +199,7 @@ func main() { // Install Flux before starting reconcile loop if installFlux { setupLog.Info("Installing Flux components before starting reconcile loop") - installCtx, installCancel := context.WithTimeout(context.Background(), 5*time.Minute) + installCtx, installCancel := context.WithTimeout(mgrCtx, 5*time.Minute) defer installCancel() // Use direct client for pre-start operations (cache is not ready yet) @@ -210,7 +213,7 @@ func main() { // Generate and install platform source resource if specified if platformSourceURL != "" { setupLog.Info("Generating platform source resource", "url", platformSourceURL, "name", platformSourceName, "ref", platformSourceRef) - installCtx, installCancel := context.WithTimeout(context.Background(), 2*time.Minute) + installCtx, installCancel := context.WithTimeout(mgrCtx, 2*time.Minute) defer installCancel() // Use direct client for pre-start operations (cache is not ready yet) @@ -292,7 +295,6 @@ func main() { } setupLog.Info("Starting controller manager") - mgrCtx := ctrl.SetupSignalHandler() if err := mgr.Start(mgrCtx); err != nil { setupLog.Error(err, "problem running manager") os.Exit(1) diff --git a/internal/crdinstall/install.go b/internal/crdinstall/install.go index f65abf63..5143f2e6 100644 --- a/internal/crdinstall/install.go +++ b/internal/crdinstall/install.go @@ -83,9 +83,9 @@ func Install(ctx context.Context, k8sClient client.Client, writeEmbeddedManifest // Validate all objects are CRDs — reject anything else to prevent // accidental force-apply of arbitrary resources. for _, obj := range objects { - if obj.GetKind() != "CustomResourceDefinition" { - return fmt.Errorf("unexpected object %s/%s in CRD manifests, only CustomResourceDefinition is allowed", - obj.GetKind(), obj.GetName()) + if obj.GetAPIVersion() != "apiextensions.k8s.io/v1" || obj.GetKind() != "CustomResourceDefinition" { + return fmt.Errorf("unexpected object %s %s/%s in CRD manifests, only apiextensions.k8s.io/v1 CustomResourceDefinition is allowed", + obj.GetAPIVersion(), obj.GetKind(), obj.GetName()) } } diff --git a/internal/manifestutil/crd_test.go b/internal/manifestutil/crd_test.go index 874377b9..c67b2efa 100644 --- a/internal/manifestutil/crd_test.go +++ b/internal/manifestutil/crd_test.go @@ -18,6 +18,7 @@ package manifestutil import ( "context" + "strings" "testing" "time" @@ -160,7 +161,7 @@ func TestWaitForCRDsEstablished_timeout(t *testing.T) { if err == nil { t.Fatal("WaitForCRDsEstablished() expected error on timeout, got nil") } - if !contains(err.Error(), "packages.cozystack.io") { + if !strings.Contains(err.Error(), "packages.cozystack.io") { t.Errorf("error should mention stuck CRD name, got: %v", err) } } @@ -176,15 +177,3 @@ func TestWaitForCRDsEstablished_empty(t *testing.T) { } } -func contains(s, substr string) bool { - return len(s) >= len(substr) && searchString(s, substr) -} - -func searchString(s, substr string) bool { - for i := 0; i <= len(s)-len(substr); i++ { - if s[i:i+len(substr)] == substr { - return true - } - } - return false -}