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 <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
This commit is contained in:
Aleksei Sviridkin 2026-02-18 00:13:25 +03:00
parent abd644122f
commit cecc5861af
No known key found for this signature in database
GPG key ID: 7988329FDF395282
3 changed files with 11 additions and 20 deletions

View file

@ -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)

View file

@ -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())
}
}

View file

@ -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
}