From ed564d411f8a2c0e38bee538b4d721641ee16287 Mon Sep 17 00:00:00 2001 From: panxiao81 Date: Thu, 10 Sep 2026 08:44:39 +0000 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=E5=88=9D=E5=A7=8B=E5=8C=96=20contr?= =?UTF-8?q?oller=20=E7=8A=B6=E6=80=81=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/v1alpha1/condition_types.go | 34 +++++++++++++++++++ .../postgresqlinstance_controller.go | 27 +++++++++++++-- .../postgresqlinstance_controller_test.go | 17 ++++++++-- .../controller/postgresqltenant_controller.go | 26 ++++++++++++-- .../postgresqltenant_controller_test.go | 18 ++++++++-- internal/controller/status.go | 34 +++++++++++++++++++ 6 files changed, 148 insertions(+), 8 deletions(-) create mode 100644 api/v1alpha1/condition_types.go create mode 100644 internal/controller/status.go diff --git a/api/v1alpha1/condition_types.go b/api/v1alpha1/condition_types.go new file mode 100644 index 0000000..4e01621 --- /dev/null +++ b/api/v1alpha1/condition_types.go @@ -0,0 +1,34 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package v1alpha1 + +const ( + // ConditionTypeReady is the stable condition consumers use for readiness. + ConditionTypeReady = "Ready" + + ReasonReconciling = "Reconciling" + ReasonReady = "Ready" + ReasonInvalidSpec = "InvalidSpec" + ReasonImmutableField = "ImmutableField" + ReasonDependencyUnavailable = "DependencyUnavailable" + ReasonAuthenticationFailed = "AuthenticationFailed" + ReasonInsufficientPrivileges = "InsufficientPrivileges" + ReasonInstanceNotReady = "InstanceNotReady" + ReasonConflict = "Conflict" + ReasonProvisioningFailed = "ProvisioningFailed" + ReasonCredentialProjectionFailed = "CredentialProjectionFailed" +) diff --git a/internal/controller/postgresqlinstance_controller.go b/internal/controller/postgresqlinstance_controller.go index d0e4f2d..cabbc31 100644 --- a/internal/controller/postgresqlinstance_controller.go +++ b/internal/controller/postgresqlinstance_controller.go @@ -18,7 +18,9 @@ package controller import ( "context" + "reflect" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -47,9 +49,30 @@ type PostgreSQLInstanceReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.24.1/pkg/reconcile func (r *PostgreSQLInstanceReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = logf.FromContext(ctx) + logger := logf.FromContext(ctx) + instance := &databasev1alpha1.PostgreSQLInstance{} + if err := r.Get(ctx, req.NamespacedName, instance); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } - // TODO(user): your logic here + before := instance.DeepCopy() + + if !instance.DeletionTimestamp.IsZero() { + instance.Status.Phase = databasev1alpha1.PostgreSQLInstancePhaseDeleting + setReconcilingCondition(&instance.Status.Conditions, instance.Generation, "instance deletion is reconciling") + } else if instance.Status.Phase == "" { + instance.Status.Phase = databasev1alpha1.PostgreSQLInstancePhaseValidating + setReconcilingCondition(&instance.Status.Conditions, instance.Generation, "instance dependencies are being validated") + } + + if !reflect.DeepEqual(before.Status, instance.Status) { + if err := r.Status().Patch(ctx, instance, client.MergeFrom(before)); err != nil { + if apierrors.IsConflict(err) { + logger.V(1).Info("instance status changed concurrently; retrying") + } + return ctrl.Result{}, err + } + } return ctrl.Result{}, nil } diff --git a/internal/controller/postgresqlinstance_controller_test.go b/internal/controller/postgresqlinstance_controller_test.go index 592b6a1..515c7d6 100644 --- a/internal/controller/postgresqlinstance_controller_test.go +++ b/internal/controller/postgresqlinstance_controller_test.go @@ -85,8 +85,21 @@ var _ = Describe("PostgreSQLInstance Controller", func() { NamespacedName: typeNamespacedName, }) Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. + actual := &databasev1alpha1.PostgreSQLInstance{} + Expect(k8sClient.Get(ctx, typeNamespacedName, actual)).To(Succeed()) + Expect(actual.Status.Phase).To(Equal(databasev1alpha1.PostgreSQLInstancePhaseValidating)) + Expect(actual.Status.Conditions).To(ConsistOf(And( + HaveField("Type", databasev1alpha1.ConditionTypeReady), + HaveField("Status", metav1.ConditionUnknown), + HaveField("Reason", databasev1alpha1.ReasonReconciling), + HaveField("ObservedGeneration", actual.Generation), + ))) + + resourceVersion := actual.ResourceVersion + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient.Get(ctx, typeNamespacedName, actual)).To(Succeed()) + Expect(actual.ResourceVersion).To(Equal(resourceVersion), "an unchanged status must not be patched") }) }) }) diff --git a/internal/controller/postgresqltenant_controller.go b/internal/controller/postgresqltenant_controller.go index 87c44ca..d3cef12 100644 --- a/internal/controller/postgresqltenant_controller.go +++ b/internal/controller/postgresqltenant_controller.go @@ -18,7 +18,9 @@ package controller import ( "context" + "reflect" + apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/runtime" ctrl "sigs.k8s.io/controller-runtime" "sigs.k8s.io/controller-runtime/pkg/client" @@ -47,9 +49,29 @@ type PostgreSQLTenantReconciler struct { // For more details, check Reconcile and its Result here: // - https://pkg.go.dev/sigs.k8s.io/controller-runtime@v0.24.1/pkg/reconcile func (r *PostgreSQLTenantReconciler) Reconcile(ctx context.Context, req ctrl.Request) (ctrl.Result, error) { - _ = logf.FromContext(ctx) + logger := logf.FromContext(ctx) + tenant := &databasev1alpha1.PostgreSQLTenant{} + if err := r.Get(ctx, req.NamespacedName, tenant); err != nil { + return ctrl.Result{}, client.IgnoreNotFound(err) + } - // TODO(user): your logic here + before := tenant.DeepCopy() + if !tenant.DeletionTimestamp.IsZero() { + tenant.Status.Phase = databasev1alpha1.PostgreSQLTenantPhaseDeleting + setReconcilingCondition(&tenant.Status.Conditions, tenant.Generation, "tenant deletion is reconciling") + } else if tenant.Status.Phase == "" { + tenant.Status.Phase = databasev1alpha1.PostgreSQLTenantPhasePending + setReconcilingCondition(&tenant.Status.Conditions, tenant.Generation, "tenant is waiting for its instance") + } + + if !reflect.DeepEqual(before.Status, tenant.Status) { + if err := r.Status().Patch(ctx, tenant, client.MergeFrom(before)); err != nil { + if apierrors.IsConflict(err) { + logger.V(1).Info("tenant status changed concurrently; retrying") + } + return ctrl.Result{}, err + } + } return ctrl.Result{}, nil } diff --git a/internal/controller/postgresqltenant_controller_test.go b/internal/controller/postgresqltenant_controller_test.go index d227b51..1f4b50f 100644 --- a/internal/controller/postgresqltenant_controller_test.go +++ b/internal/controller/postgresqltenant_controller_test.go @@ -82,8 +82,22 @@ var _ = Describe("PostgreSQLTenant Controller", func() { NamespacedName: typeNamespacedName, }) Expect(err).NotTo(HaveOccurred()) - // TODO(user): Add more specific assertions depending on your controller's reconciliation logic. - // Example: If you expect a certain status condition after reconciliation, verify it here. + + actual := &databasev1alpha1.PostgreSQLTenant{} + Expect(k8sClient.Get(ctx, typeNamespacedName, actual)).To(Succeed()) + Expect(actual.Status.Phase).To(Equal(databasev1alpha1.PostgreSQLTenantPhasePending)) + Expect(actual.Status.Conditions).To(ConsistOf(And( + HaveField("Type", databasev1alpha1.ConditionTypeReady), + HaveField("Status", metav1.ConditionUnknown), + HaveField("Reason", databasev1alpha1.ReasonReconciling), + HaveField("ObservedGeneration", actual.Generation), + ))) + + resourceVersion := actual.ResourceVersion + _, err = controllerReconciler.Reconcile(ctx, reconcile.Request{NamespacedName: typeNamespacedName}) + Expect(err).NotTo(HaveOccurred()) + Expect(k8sClient.Get(ctx, typeNamespacedName, actual)).To(Succeed()) + Expect(actual.ResourceVersion).To(Equal(resourceVersion), "an unchanged status must not be patched") }) }) }) diff --git a/internal/controller/status.go b/internal/controller/status.go new file mode 100644 index 0000000..a2d6456 --- /dev/null +++ b/internal/controller/status.go @@ -0,0 +1,34 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + apiMeta "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + databasev1alpha1 "git.ddupan.top/panxiao81/postgresql-tenant-operator/api/v1alpha1" +) + +func setReconcilingCondition(conditions *[]metav1.Condition, generation int64, message string) { + apiMeta.SetStatusCondition(conditions, metav1.Condition{ + Type: databasev1alpha1.ConditionTypeReady, + Status: metav1.ConditionUnknown, + ObservedGeneration: generation, + Reason: databasev1alpha1.ReasonReconciling, + Message: message, + }) +} From 1c0be438054eab0c59198815c2dd91e9dae5f7a7 Mon Sep 17 00:00:00 2001 From: panxiao81 Date: Thu, 10 Sep 2026 08:58:41 +0000 Subject: [PATCH 2/2] =?UTF-8?q?refactor:=20=E6=8C=89=20phase=20handler=20?= =?UTF-8?q?=E7=BB=84=E7=BB=87=E7=8A=B6=E6=80=81=E6=9C=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../postgresqlinstance_controller.go | 11 +- .../postgresqlinstance_state_machine.go | 73 ++++++++++++ .../controller/postgresqltenant_controller.go | 10 +- .../postgresqltenant_state_machine.go | 77 +++++++++++++ internal/controller/state_machine_test.go | 107 ++++++++++++++++++ 5 files changed, 265 insertions(+), 13 deletions(-) create mode 100644 internal/controller/postgresqlinstance_state_machine.go create mode 100644 internal/controller/postgresqltenant_state_machine.go create mode 100644 internal/controller/state_machine_test.go diff --git a/internal/controller/postgresqlinstance_controller.go b/internal/controller/postgresqlinstance_controller.go index cabbc31..c0f2ac0 100644 --- a/internal/controller/postgresqlinstance_controller.go +++ b/internal/controller/postgresqlinstance_controller.go @@ -56,13 +56,10 @@ func (r *PostgreSQLInstanceReconciler) Reconcile(ctx context.Context, req ctrl.R } before := instance.DeepCopy() - - if !instance.DeletionTimestamp.IsZero() { - instance.Status.Phase = databasev1alpha1.PostgreSQLInstancePhaseDeleting - setReconcilingCondition(&instance.Status.Conditions, instance.Generation, "instance deletion is reconciling") - } else if instance.Status.Phase == "" { - instance.Status.Phase = databasev1alpha1.PostgreSQLInstancePhaseValidating - setReconcilingCondition(&instance.Status.Conditions, instance.Generation, "instance dependencies are being validated") + phaseResult := newInstanceStateMachine().reconcile(instance) + instance.Status.Phase = phaseResult.phase + if phaseResult.reconcilingMessage != "" { + setReconcilingCondition(&instance.Status.Conditions, instance.Generation, phaseResult.reconcilingMessage) } if !reflect.DeepEqual(before.Status, instance.Status) { diff --git a/internal/controller/postgresqlinstance_state_machine.go b/internal/controller/postgresqlinstance_state_machine.go new file mode 100644 index 0000000..ddad1c1 --- /dev/null +++ b/internal/controller/postgresqlinstance_state_machine.go @@ -0,0 +1,73 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import databasev1alpha1 "git.ddupan.top/panxiao81/postgresql-tenant-operator/api/v1alpha1" + +type instancePhaseResult struct { + phase databasev1alpha1.PostgreSQLInstancePhase + reconcilingMessage string +} + +type instancePhaseHandler func(*databasev1alpha1.PostgreSQLInstance) instancePhaseResult + +type instanceStateMachine struct { + handlers map[databasev1alpha1.PostgreSQLInstancePhase]instancePhaseHandler +} + +func newInstanceStateMachine() instanceStateMachine { + return instanceStateMachine{handlers: map[databasev1alpha1.PostgreSQLInstancePhase]instancePhaseHandler{ + databasev1alpha1.PostgreSQLInstancePhasePending: reconcileInstancePending, + databasev1alpha1.PostgreSQLInstancePhaseValidating: keepInstancePhase, + databasev1alpha1.PostgreSQLInstancePhaseInitializingRegistry: keepInstancePhase, + databasev1alpha1.PostgreSQLInstancePhaseReady: keepInstancePhase, + databasev1alpha1.PostgreSQLInstancePhaseDeleting: reconcileInstanceDeleting, + }} +} + +func (m instanceStateMachine) reconcile(instance *databasev1alpha1.PostgreSQLInstance) instancePhaseResult { + phase := instance.Status.Phase + if !instance.DeletionTimestamp.IsZero() { + phase = databasev1alpha1.PostgreSQLInstancePhaseDeleting + } else if phase == "" { + phase = databasev1alpha1.PostgreSQLInstancePhasePending + } + + handler, found := m.handlers[phase] + if !found { + return instancePhaseResult{phase: phase} + } + return handler(instance) +} + +func reconcileInstancePending(*databasev1alpha1.PostgreSQLInstance) instancePhaseResult { + return instancePhaseResult{ + phase: databasev1alpha1.PostgreSQLInstancePhaseValidating, + reconcilingMessage: "instance dependencies are being validated", + } +} + +func reconcileInstanceDeleting(*databasev1alpha1.PostgreSQLInstance) instancePhaseResult { + return instancePhaseResult{ + phase: databasev1alpha1.PostgreSQLInstancePhaseDeleting, + reconcilingMessage: "instance deletion is reconciling", + } +} + +func keepInstancePhase(instance *databasev1alpha1.PostgreSQLInstance) instancePhaseResult { + return instancePhaseResult{phase: instance.Status.Phase} +} diff --git a/internal/controller/postgresqltenant_controller.go b/internal/controller/postgresqltenant_controller.go index d3cef12..b4eaf22 100644 --- a/internal/controller/postgresqltenant_controller.go +++ b/internal/controller/postgresqltenant_controller.go @@ -56,12 +56,10 @@ func (r *PostgreSQLTenantReconciler) Reconcile(ctx context.Context, req ctrl.Req } before := tenant.DeepCopy() - if !tenant.DeletionTimestamp.IsZero() { - tenant.Status.Phase = databasev1alpha1.PostgreSQLTenantPhaseDeleting - setReconcilingCondition(&tenant.Status.Conditions, tenant.Generation, "tenant deletion is reconciling") - } else if tenant.Status.Phase == "" { - tenant.Status.Phase = databasev1alpha1.PostgreSQLTenantPhasePending - setReconcilingCondition(&tenant.Status.Conditions, tenant.Generation, "tenant is waiting for its instance") + phaseResult := newTenantStateMachine().reconcile(tenant) + tenant.Status.Phase = phaseResult.phase + if phaseResult.reconcilingMessage != "" { + setReconcilingCondition(&tenant.Status.Conditions, tenant.Generation, phaseResult.reconcilingMessage) } if !reflect.DeepEqual(before.Status, tenant.Status) { diff --git a/internal/controller/postgresqltenant_state_machine.go b/internal/controller/postgresqltenant_state_machine.go new file mode 100644 index 0000000..25ed1d0 --- /dev/null +++ b/internal/controller/postgresqltenant_state_machine.go @@ -0,0 +1,77 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import databasev1alpha1 "git.ddupan.top/panxiao81/postgresql-tenant-operator/api/v1alpha1" + +type tenantPhaseResult struct { + phase databasev1alpha1.PostgreSQLTenantPhase + reconcilingMessage string +} + +type tenantPhaseHandler func(*databasev1alpha1.PostgreSQLTenant) tenantPhaseResult + +type tenantStateMachine struct { + handlers map[databasev1alpha1.PostgreSQLTenantPhase]tenantPhaseHandler +} + +func newTenantStateMachine() tenantStateMachine { + return tenantStateMachine{handlers: map[databasev1alpha1.PostgreSQLTenantPhase]tenantPhaseHandler{ + databasev1alpha1.PostgreSQLTenantPhasePending: reconcileTenantPending, + databasev1alpha1.PostgreSQLTenantPhasePlanned: keepTenantPhase, + databasev1alpha1.PostgreSQLTenantPhaseCredentialCreated: keepTenantPhase, + databasev1alpha1.PostgreSQLTenantPhaseRoleCreated: keepTenantPhase, + databasev1alpha1.PostgreSQLTenantPhaseDatabaseCreated: keepTenantPhase, + databasev1alpha1.PostgreSQLTenantPhaseExternalSecretCreated: keepTenantPhase, + databasev1alpha1.PostgreSQLTenantPhaseCredentialProjected: keepTenantPhase, + databasev1alpha1.PostgreSQLTenantPhaseReady: keepTenantPhase, + databasev1alpha1.PostgreSQLTenantPhaseDeleting: reconcileTenantDeleting, + }} +} + +func (m tenantStateMachine) reconcile(tenant *databasev1alpha1.PostgreSQLTenant) tenantPhaseResult { + phase := tenant.Status.Phase + if !tenant.DeletionTimestamp.IsZero() { + phase = databasev1alpha1.PostgreSQLTenantPhaseDeleting + } else if phase == "" { + phase = databasev1alpha1.PostgreSQLTenantPhasePending + } + + handler, found := m.handlers[phase] + if !found { + return tenantPhaseResult{phase: phase} + } + return handler(tenant) +} + +func reconcileTenantPending(*databasev1alpha1.PostgreSQLTenant) tenantPhaseResult { + return tenantPhaseResult{ + phase: databasev1alpha1.PostgreSQLTenantPhasePending, + reconcilingMessage: "tenant is waiting for its instance", + } +} + +func reconcileTenantDeleting(*databasev1alpha1.PostgreSQLTenant) tenantPhaseResult { + return tenantPhaseResult{ + phase: databasev1alpha1.PostgreSQLTenantPhaseDeleting, + reconcilingMessage: "tenant deletion is reconciling", + } +} + +func keepTenantPhase(tenant *databasev1alpha1.PostgreSQLTenant) tenantPhaseResult { + return tenantPhaseResult{phase: tenant.Status.Phase} +} diff --git a/internal/controller/state_machine_test.go b/internal/controller/state_machine_test.go new file mode 100644 index 0000000..ba1c3a6 --- /dev/null +++ b/internal/controller/state_machine_test.go @@ -0,0 +1,107 @@ +/* +Copyright 2026. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package controller + +import ( + "time" + + . "github.com/onsi/ginkgo/v2" + . "github.com/onsi/gomega" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + + databasev1alpha1 "git.ddupan.top/panxiao81/postgresql-tenant-operator/api/v1alpha1" +) + +var _ = Describe("phase handler state machines", func() { + DescribeTable("dispatches Instance phases", + func(instance *databasev1alpha1.PostgreSQLInstance, expected databasev1alpha1.PostgreSQLInstancePhase, hasMessage bool) { + result := newInstanceStateMachine().reconcile(instance) + Expect(result.phase).To(Equal(expected)) + if hasMessage { + Expect(result.reconcilingMessage).NotTo(BeEmpty()) + } else { + Expect(result.reconcilingMessage).To(BeEmpty()) + } + }, + Entry("starts validation from an empty checkpoint", + &databasev1alpha1.PostgreSQLInstance{}, + databasev1alpha1.PostgreSQLInstancePhaseValidating, + true, + ), + Entry("keeps an active phase until its handler can observe dependencies", + &databasev1alpha1.PostgreSQLInstance{Status: databasev1alpha1.PostgreSQLInstanceStatus{ + Phase: databasev1alpha1.PostgreSQLInstancePhaseInitializingRegistry, + }}, + databasev1alpha1.PostgreSQLInstancePhaseInitializingRegistry, + false, + ), + Entry("routes deletion independently of the previous phase", + deletingInstance(), + databasev1alpha1.PostgreSQLInstancePhaseDeleting, + true, + ), + ) + + DescribeTable("dispatches Tenant phases", + func(tenant *databasev1alpha1.PostgreSQLTenant, expected databasev1alpha1.PostgreSQLTenantPhase, hasMessage bool) { + result := newTenantStateMachine().reconcile(tenant) + Expect(result.phase).To(Equal(expected)) + if hasMessage { + Expect(result.reconcilingMessage).NotTo(BeEmpty()) + } else { + Expect(result.reconcilingMessage).To(BeEmpty()) + } + }, + Entry("starts pending from an empty checkpoint", + &databasev1alpha1.PostgreSQLTenant{}, + databasev1alpha1.PostgreSQLTenantPhasePending, + true, + ), + Entry("keeps an active phase until its handler can observe dependencies", + &databasev1alpha1.PostgreSQLTenant{Status: databasev1alpha1.PostgreSQLTenantStatus{ + Phase: databasev1alpha1.PostgreSQLTenantPhaseCredentialCreated, + }}, + databasev1alpha1.PostgreSQLTenantPhaseCredentialCreated, + false, + ), + Entry("routes deletion independently of the previous phase", + deletingTenant(), + databasev1alpha1.PostgreSQLTenantPhaseDeleting, + true, + ), + ) +}) + +func deletingInstance() *databasev1alpha1.PostgreSQLInstance { + deletionTimestamp := metav1.NewTime(time.Now()) + return &databasev1alpha1.PostgreSQLInstance{ + ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &deletionTimestamp}, + Status: databasev1alpha1.PostgreSQLInstanceStatus{ + Phase: databasev1alpha1.PostgreSQLInstancePhaseReady, + }, + } +} + +func deletingTenant() *databasev1alpha1.PostgreSQLTenant { + deletionTimestamp := metav1.NewTime(time.Now()) + return &databasev1alpha1.PostgreSQLTenant{ + ObjectMeta: metav1.ObjectMeta{DeletionTimestamp: &deletionTimestamp}, + Status: databasev1alpha1.PostgreSQLTenantStatus{ + Phase: databasev1alpha1.PostgreSQLTenantPhaseReady, + }, + } +}