Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions internal/controller/modern_platform_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,7 @@ func TestReconcileCNPGClusterRemovesStaleProjectionAfterProjectRecreation(t *tes
t.Fatal(err)
}
desired := cnpgresources.BuildCluster(project, &project.Status.SecretNames)
desired.Default()
if !apiequality.Semantic.DeepEqual(updated.Spec.PostgresConfiguration.Parameters, desired.Spec.PostgresConfiguration.Parameters) ||
!apiequality.Semantic.DeepEqual(updated.Spec.PostgresConfiguration.PgHBA, desired.Spec.PostgresConfiguration.PgHBA) ||
!apiequality.Semantic.DeepEqual(updated.Spec.PostgresConfiguration.AdditionalLibraries, desired.Spec.PostgresConfiguration.AdditionalLibraries) ||
Expand Down Expand Up @@ -234,6 +235,32 @@ func TestReconcileCNPGClusterRemovesOnlyProjectOwnerAndPreservesLabels(t *testin
}
}

func TestReconcileCNPGClusterConvergesWithAdmissionDefaultedRetainedCluster(t *testing.T) {
t.Parallel()

scheme := newPowerSyncTestScheme(t)
project := &supabasev1alpha1.SupabaseProject{
ObjectMeta: metav1.ObjectMeta{Name: "retained", Namespace: "default"},
Status: supabasev1alpha1.SupabaseProjectStatus{SecretNames: supabasev1alpha1.SecretNamesStatus{
SupabaseAdmin: "retained-admin", Authenticator: "retained-authenticator", AuthAdmin: "retained-auth-admin",
}},
}
existing := cnpgresources.BuildCluster(project, &project.Status.SecretNames)
existing.Default() // Simulate CNPG admission defaults on a retained Cluster.
reconciler := &SupabaseProjectReconciler{
Client: fake.NewClientBuilder().WithScheme(scheme).WithObjects(project, existing).Build(),
Scheme: scheme,
}

result, err := reconciler.reconcileCNPGCluster(context.Background(), project)
if err != nil {
t.Fatalf("reconcileCNPGCluster() error = %v", err)
}
if !result.IsZero() {
t.Fatalf("reconcileCNPGCluster() result = %#v, want no requeue for converged retained Cluster", result)
}
}

func TestDurableAdoptionRequiresMatchingInstanceLabel(t *testing.T) {
t.Parallel()

Expand Down
4 changes: 4 additions & 0 deletions internal/controller/supabaseproject_controller.go
Original file line number Diff line number Diff line change
Expand Up @@ -505,6 +505,10 @@ func (r *SupabaseProjectReconciler) reconcileCNPGCluster(ctx context.Context, pr
log.Info("Reconciling CNPG Cluster")

desired := cnpg.BuildCluster(project, &project.Status.SecretNames)
// CNPG admission defaults persisted Clusters before they reach this
// reconciler. Apply the same defaults to desired state so retained Clusters
// converge instead of being rewritten on every reconciliation.
desired.Default()

// Check if cluster exists
existing := &cnpgv1.Cluster{}
Expand Down
20 changes: 18 additions & 2 deletions internal/resources/cnpg/cluster.go
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ func BuildCluster(project *supabasev1alpha1.SupabaseProject, secretNames *supaba
cluster.Spec.Plugins = []cnpgv1.PluginConfiguration{
{
Name: BarmanCloudPluginName,
Enabled: ptr.To(true),
IsWALArchiver: ptr.To(true),
Parameters: map[string]string{
"barmanObjectName": ObjectStoreName(project),
Expand Down Expand Up @@ -214,7 +215,7 @@ func buildAllRoles(project *supabasev1alpha1.SupabaseProject, secretNames *supab
// - powersync_storage: stores Powersync's internal sync state (checkpoints, buckets)
// - powersync_replication: reads the WAL via logical replication for CDC
func BuildPowersyncRoles(secretNames *supabasev1alpha1.SecretNamesStatus) []cnpgv1.RoleConfiguration {
return []cnpgv1.RoleConfiguration{
return applyGeneratedRoleDefaults([]cnpgv1.RoleConfiguration{
{
Name: "powersync_storage",
Ensure: cnpgv1.EnsurePresent,
Expand All @@ -235,7 +236,7 @@ func BuildPowersyncRoles(secretNames *supabasev1alpha1.SecretNamesStatus) []cnpg
},
Comment: "Powersync CDC replication role",
},
}
})
}

// buildRoles creates the managed roles for Supabase
Expand Down Expand Up @@ -309,12 +310,27 @@ func buildRoles(spec *supabasev1alpha1.DatabaseSpec, secretNames *supabasev1alph
},
}

// Apply CNPG CRD schema defaults only to roles generated by this operator.
// User-supplied additional roles have already been defaulted by the
// SupabaseProject admission schema and may intentionally set zero values.
roles = applyGeneratedRoleDefaults(roles)

// Add additional custom roles (directly from CNPG RoleConfiguration)
roles = append(roles, spec.AdditionalRoles...)

return roles
}

func applyGeneratedRoleDefaults(roles []cnpgv1.RoleConfiguration) []cnpgv1.RoleConfiguration {
for i := range roles {
roles[i].ConnectionLimit = -1
if roles[i].Inherit == nil {
roles[i].Inherit = ptr.To(true)
}
}
return roles
}

// defaultParameters returns the default PostgreSQL parameters
func defaultParameters() map[string]string {
return map[string]string{
Expand Down
Loading