From 58e2b646be50f4de7ebe1ceb41771a2c8b9c5676 Mon Sep 17 00:00:00 2001 From: Aleksei Sviridkin Date: Thu, 19 Feb 2026 17:54:33 +0300 Subject: [PATCH] fix(operator): use SSA for PackageSource, separate context, unconditional creation Switch installPlatformPackageSource to server-side apply (SSA) with field manager, matching the pattern used in crdinstall. SSA is idempotent and preserves metadata fields managed by other controllers. Create PackageSource unconditionally (not only when platformSourceURL is set), matching the previous Helm template behavior where PackageSource was always created regardless of source URL configuration. Use a dedicated context with its own 2-minute timeout for PackageSource creation, separate from the platform source resource installation. Co-Authored-By: Claude Signed-off-by: Aleksei Sviridkin --- cmd/cozystack-operator/main.go | 55 +++++++++++++---------------- cmd/cozystack-operator/main_test.go | 8 +++++ 2 files changed, 32 insertions(+), 31 deletions(-) diff --git a/cmd/cozystack-operator/main.go b/cmd/cozystack-operator/main.go index fb45b96c..10102bc0 100644 --- a/cmd/cozystack-operator/main.go +++ b/cmd/cozystack-operator/main.go @@ -222,24 +222,28 @@ func main() { } else { setupLog.Info("Platform source resource installation completed successfully") } + } - // Create platform PackageSource that references the source resource - setupLog.Info("Creating platform PackageSource", "platformSourceName", platformSourceName) - sourceType, _, err := parsePlatformSourceURL(platformSourceURL) - if err != nil { - setupLog.Error(err, "failed to parse platform source URL for PackageSource creation") - os.Exit(1) - } + // Create platform PackageSource unconditionally (it was previously created + // by a Helm template regardless of platformSourceURL). Derive sourceRefKind + // from URL when available, default to OCIRepository otherwise. + { sourceRefKind := "OCIRepository" - if sourceType == "git" { - sourceRefKind = "GitRepository" + if platformSourceURL != "" { + sourceType, _, _ := parsePlatformSourceURL(platformSourceURL) + // Error already checked by installPlatformSourceResource above + if sourceType == "git" { + sourceRefKind = "GitRepository" + } } - if err := installPlatformPackageSource(installCtx, directClient, platformSourceName, sourceRefKind); err != nil { + setupLog.Info("Creating platform PackageSource", "platformSourceName", platformSourceName) + psCtx, psCancel := context.WithTimeout(mgrCtx, 2*time.Minute) + defer psCancel() + if err := installPlatformPackageSource(psCtx, directClient, platformSourceName, sourceRefKind); err != nil { setupLog.Error(err, "failed to create platform PackageSource") os.Exit(1) - } else { - setupLog.Info("Platform PackageSource creation completed successfully") } + setupLog.Info("Platform PackageSource creation completed successfully") } // Setup PackageSource reconciler @@ -663,26 +667,15 @@ func installPlatformPackageSource(ctx context.Context, k8sClient client.Client, logger.Info("Applying platform PackageSource", "name", packageSourceName) - existing := &cozyv1alpha1.PackageSource{} - key := client.ObjectKeyFromObject(ps) - - err := k8sClient.Get(ctx, key, existing) - if err != nil { - if client.IgnoreNotFound(err) == nil { - if err := k8sClient.Create(ctx, ps); err != nil { - return fmt.Errorf("failed to create PackageSource %s: %w", packageSourceName, err) - } - logger.Info("Created platform PackageSource", "name", packageSourceName) - } else { - return fmt.Errorf("failed to check if PackageSource exists: %w", err) - } - } else { - ps.SetResourceVersion(existing.GetResourceVersion()) - if err := k8sClient.Update(ctx, ps); err != nil { - return fmt.Errorf("failed to update PackageSource %s: %w", packageSourceName, err) - } - logger.Info("Updated platform PackageSource", "name", packageSourceName) + patchOptions := &client.PatchOptions{ + FieldManager: "cozystack-operator", + Force: func() *bool { b := true; return &b }(), } + if err := k8sClient.Patch(ctx, ps, client.Apply, patchOptions); err != nil { + return fmt.Errorf("failed to apply PackageSource %s: %w", packageSourceName, err) + } + + logger.Info("Applied platform PackageSource", "name", packageSourceName) return nil } diff --git a/cmd/cozystack-operator/main_test.go b/cmd/cozystack-operator/main_test.go index da1769f2..41b0655f 100644 --- a/cmd/cozystack-operator/main_test.go +++ b/cmd/cozystack-operator/main_test.go @@ -96,6 +96,9 @@ func TestInstallPlatformPackageSource_Updates(t *testing.T) { ObjectMeta: metav1.ObjectMeta{ Name: "cozystack.cozystack-platform", ResourceVersion: "1", + Labels: map[string]string{ + "custom-label": "should-be-preserved", + }, }, Spec: cozyv1alpha1.PackageSourceSpec{ SourceRef: &cozyv1alpha1.PackageSourceRef{ @@ -127,6 +130,11 @@ func TestInstallPlatformPackageSource_Updates(t *testing.T) { if len(ps.Spec.Variants) != 4 { t.Errorf("expected 4 variants after update, got %d", len(ps.Spec.Variants)) } + + // Verify that labels set by other controllers are preserved (SSA does not overwrite unmanaged fields) + if ps.Labels["custom-label"] != "should-be-preserved" { + t.Errorf("expected custom-label to be preserved, got %q", ps.Labels["custom-label"]) + } } func TestInstallPlatformPackageSource_GitRepository(t *testing.T) {