This is an automated email from the ASF dual-hosted git repository.

pcongiusti pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel-k.git


The following commit(s) were added to refs/heads/main by this push:
     new d9703d910 fix(ctrl): use build complete and deploy phase
d9703d910 is described below

commit d9703d910eca3c022105c20afdf4f20c5cd8199e
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Wed Mar 18 13:22:02 2026 +0100

    fix(ctrl): use build complete and deploy phase
    
    Closes #6515
---
 docs/modules/ROOT/pages/running/dry-build.adoc     |  4 +-
 pkg/apis/camel/v1/integration_types.go             |  2 +
 pkg/apis/camel/v1/integration_types_support.go     | 18 +++--
 pkg/cmd/deploy.go                                  |  6 +-
 pkg/cmd/undeploy.go                                |  2 +-
 pkg/controller/integration/build.go                |  2 +-
 pkg/controller/integration/build_complete.go       | 67 ++++++++++++++++
 pkg/controller/integration/build_complete_test.go  | 89 ++++++++++++++++++++++
 pkg/controller/integration/build_kit.go            |  7 +-
 pkg/controller/integration/build_kit_test.go       |  4 +-
 pkg/controller/integration/build_test.go           |  2 +-
 pkg/controller/integration/initialize.go           | 24 ++++--
 .../integration/integration_controller.go          |  1 +
 pkg/controller/integration/monitor.go              | 12 +--
 pkg/trait/gc.go                                    | 35 +--------
 pkg/trait/gc_test.go                               | 50 +-----------
 pkg/trait/gitops.go                                |  2 +-
 pkg/trait/gitops_test.go                           |  2 +-
 18 files changed, 205 insertions(+), 124 deletions(-)

diff --git a/docs/modules/ROOT/pages/running/dry-build.adoc 
b/docs/modules/ROOT/pages/running/dry-build.adoc
index 9093bb2ce..00748d0ec 100644
--- a/docs/modules/ROOT/pages/running/dry-build.adoc
+++ b/docs/modules/ROOT/pages/running/dry-build.adoc
@@ -24,12 +24,12 @@ As the patching on an Integration custom resource can be 
boring, we've introduce
 [[undeploy]]
 == Undeploy an application
 
-Specular to that, we have the **undeploy** operation. If at any point in time 
you want to bring the Integration back to its original status, then just patch 
its phase to an empty string (which represent the `Initialization` phase). The 
operator will take care to revert it and to clean all the resources associated.
+Specular to that, we have the **undeploy** operation. If at any point in time 
you want to bring the Integration back to its original status, then just patch 
its phase to the `Undeploying` phase. The operator will take care to revert it 
and to clean all the resources associated.
 
 It would be something like:
 
 ```bash
-kubectl patch it my-app --type=merge --subresource=status   -p 
'{"status":{"phase":""}}'
+kubectl patch it my-app --type=merge --subresource=status   -p 
'{"status":{"phase":"Undeploying"}}'
 ```
 
 Also here, you will find handy the `kamel undeploy` CLI command. It also 
expects one ore more Integration names you want to undeploy. The `kamel 
undeploy` and the patch to "" are equivalent.
diff --git a/pkg/apis/camel/v1/integration_types.go 
b/pkg/apis/camel/v1/integration_types.go
index 554de5cfb..4fb96bbc3 100644
--- a/pkg/apis/camel/v1/integration_types.go
+++ b/pkg/apis/camel/v1/integration_types.go
@@ -177,6 +177,8 @@ const (
        IntegrationPhaseBuildComplete IntegrationPhase = "Build Complete"
        // IntegrationPhaseDeploying --.
        IntegrationPhaseDeploying IntegrationPhase = "Deploying"
+       // IntegrationPhaseUnDeploying --.
+       IntegrationPhaseUnDeploying IntegrationPhase = "Undeploying"
        // IntegrationPhaseRunning --.
        IntegrationPhaseRunning IntegrationPhase = "Running"
        // IntegrationPhaseError --.
diff --git a/pkg/apis/camel/v1/integration_types_support.go 
b/pkg/apis/camel/v1/integration_types_support.go
index 2e7fa71c7..48a717070 100644
--- a/pkg/apis/camel/v1/integration_types_support.go
+++ b/pkg/apis/camel/v1/integration_types_support.go
@@ -370,16 +370,18 @@ func (in *Integration) IsSynthetic() bool {
        return in.Annotations[IntegrationSyntheticLabel] == "true"
 }
 
-// SetBuildOrDeploymentPhase set the proper building phase and the related 
timestamps.
-func (in *Integration) SetBuildOrDeploymentPhase() {
+// SetBuildCompletePhase set the proper building phase and the related 
timestamps.
+func (in *Integration) SetBuildCompletePhase() {
        now := metav1.Now().Rfc3339Copy()
        in.Status.BuildTimestamp = &now
-       if in.Annotations[IntegrationDontRunAfterBuildAnnotation] == 
IntegrationDontRunAfterBuildAnnotationTrueValue {
-               in.Status.Phase = IntegrationPhaseBuildComplete
-       } else {
-               in.Status.DeploymentTimestamp = &now
-               in.Status.Phase = IntegrationPhaseDeploying
-       }
+       in.Status.Phase = IntegrationPhaseBuildComplete
+}
+
+// SetDeployingPhase set the proper deploy phase and the related timestamps.
+func (in *Integration) SetDeployingPhase() {
+       now := metav1.Now().Rfc3339Copy()
+       in.Status.DeploymentTimestamp = &now
+       in.Status.Phase = IntegrationPhaseDeploying
 }
 
 // GetCondition returns the condition with the provided type.
diff --git a/pkg/cmd/deploy.go b/pkg/cmd/deploy.go
index f63d36b2c..cc775efb6 100644
--- a/pkg/cmd/deploy.go
+++ b/pkg/cmd/deploy.go
@@ -23,7 +23,6 @@ import (
 
        v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
        "github.com/spf13/cobra"
-       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
        ctrl "sigs.k8s.io/controller-runtime/pkg/client"
 )
 
@@ -74,10 +73,7 @@ func (o *deployCmdOptions) run(cmd *cobra.Command, args 
[]string) error {
        }
 
        integration := existing.DeepCopy()
-       // Set DeploymentTimestamp to track when deployment was initiated
-       now := metav1.Now().Rfc3339Copy()
-       integration.Status.DeploymentTimestamp = &now
-       integration.Status.Phase = v1.IntegrationPhaseDeploying
+       integration.SetDeployingPhase()
 
        patch := ctrl.MergeFrom(existing)
        d, err := patch.Data(integration)
diff --git a/pkg/cmd/undeploy.go b/pkg/cmd/undeploy.go
index fcb7495da..4d989ad02 100644
--- a/pkg/cmd/undeploy.go
+++ b/pkg/cmd/undeploy.go
@@ -98,7 +98,7 @@ func (o *undeployCmdOptions) undeployIntegrations(cmd 
*cobra.Command, c k8sclien
                        continue
                }
                it := i
-               it.Status.Phase = v1.IntegrationPhaseInitialization
+               it.Status.Phase = v1.IntegrationPhaseUnDeploying
                if err := c.Status().Update(o.Context, &it); err != nil {
                        return undeployed, fmt.Errorf("could not undeploy %s in 
namespace %s: %w", it.Name, o.Namespace, err)
                }
diff --git a/pkg/controller/integration/build.go 
b/pkg/controller/integration/build.go
index 2d1971de5..a82dcc20f 100644
--- a/pkg/controller/integration/build.go
+++ b/pkg/controller/integration/build.go
@@ -228,7 +228,7 @@ func (action *buildAction) handleBuildRunning(ctx 
context.Context, it *v1.Integr
                        }
                        it.Status.Image = fmt.Sprintf("%s@%s", image, 
build.Status.Digest)
                }
-               it.SetBuildOrDeploymentPhase()
+               it.SetBuildCompletePhase()
        case v1.BuildPhaseError, v1.BuildPhaseInterrupted, v1.BuildPhaseFailed:
                it.Status.Phase = v1.IntegrationPhaseError
                reason := fmt.Sprintf("Build%s", build.Status.Phase)
diff --git a/pkg/controller/integration/build_complete.go 
b/pkg/controller/integration/build_complete.go
new file mode 100644
index 000000000..edc1fa17f
--- /dev/null
+++ b/pkg/controller/integration/build_complete.go
@@ -0,0 +1,67 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 integration
+
+import (
+       "context"
+
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/v2/pkg/trait"
+       corev1 "k8s.io/api/core/v1"
+)
+
+// NewBuildCompleteAction creates a new build complete action.
+func NewBuildCompleteAction() Action {
+       return &buildCompleteAction{}
+}
+
+type buildCompleteAction struct {
+       baseAction
+}
+
+// Name returns a common name of the action.
+func (action *buildCompleteAction) Name() string {
+       return "build-complete"
+}
+
+// CanHandle tells whether this action can handle the integration.
+func (action *buildCompleteAction) CanHandle(integration *v1.Integration) bool 
{
+       return integration.Status.Phase == v1.IntegrationPhaseBuildComplete
+}
+
+// Handle handles the integrations.
+func (action *buildCompleteAction) Handle(ctx context.Context, integration 
*v1.Integration) (*v1.Integration, error) {
+       // Run traits that are enabled for the "Build Complete" phase (ie, 
gitops)
+       _, err := trait.Apply(ctx, action.client, integration, nil)
+       if err != nil {
+               integration.Status.Phase = v1.IntegrationPhaseError
+               integration.SetReadyCondition(
+                       corev1.ConditionFalse,
+                       v1.IntegrationConditionInitializationFailedReason,
+                       err.Error(),
+               )
+
+               return integration, err
+       }
+       if integration.Annotations[v1.IntegrationDontRunAfterBuildAnnotation] 
!= v1.IntegrationDontRunAfterBuildAnnotationTrueValue {
+               // We only move to Deploying phase if the Integration is not 
marked as "build only"
+               integration.SetDeployingPhase()
+       }
+
+       return integration, nil
+}
diff --git a/pkg/controller/integration/build_complete_test.go 
b/pkg/controller/integration/build_complete_test.go
new file mode 100644
index 000000000..a0fe9cf37
--- /dev/null
+++ b/pkg/controller/integration/build_complete_test.go
@@ -0,0 +1,89 @@
+/*
+Licensed to the Apache Software Foundation (ASF) under one or more
+contributor license agreements.  See the NOTICE file distributed with
+this work for additional information regarding copyright ownership.
+The ASF licenses this file to You 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 integration
+
+import (
+       "context"
+       "testing"
+
+       v1 "github.com/apache/camel-k/v2/pkg/apis/camel/v1"
+       "github.com/apache/camel-k/v2/pkg/internal"
+       "github.com/apache/camel-k/v2/pkg/util/log"
+       "github.com/stretchr/testify/assert"
+       "github.com/stretchr/testify/require"
+       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestIntegrationBuildCompleteToDeployDrift(t *testing.T) {
+       it := &v1.Integration{
+               TypeMeta: metav1.TypeMeta{
+                       APIVersion: v1.SchemeGroupVersion.String(),
+                       Kind:       v1.IntegrationKind,
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Namespace: "ns",
+                       Name:      "my-it",
+               },
+               Status: v1.IntegrationStatus{
+                       Phase: v1.IntegrationPhaseBuildComplete,
+               },
+       }
+       c, err := internal.NewFakeClient(it)
+       require.NoError(t, err)
+
+       a := buildCompleteAction{}
+       a.InjectLogger(log.Log)
+       a.InjectClient(c)
+       assert.Equal(t, "build-complete", a.Name())
+       assert.True(t, a.CanHandle(it))
+       handledIt, err := a.Handle(context.TODO(), it)
+       require.NoError(t, err)
+       require.NotNil(t, handledIt)
+       assert.Equal(t, v1.IntegrationPhaseDeploying, handledIt.Status.Phase)
+}
+
+func TestIntegrationBuildCompleteDontDeploy(t *testing.T) {
+       it := &v1.Integration{
+               TypeMeta: metav1.TypeMeta{
+                       APIVersion: v1.SchemeGroupVersion.String(),
+                       Kind:       v1.IntegrationKind,
+               },
+               ObjectMeta: metav1.ObjectMeta{
+                       Namespace: "ns",
+                       Name:      "my-it",
+                       Annotations: map[string]string{
+                               v1.IntegrationDontRunAfterBuildAnnotation: 
"true",
+                       },
+               },
+               Status: v1.IntegrationStatus{
+                       Phase: v1.IntegrationPhaseBuildComplete,
+               },
+       }
+       c, err := internal.NewFakeClient(it)
+       require.NoError(t, err)
+
+       a := buildCompleteAction{}
+       a.InjectLogger(log.Log)
+       a.InjectClient(c)
+       assert.Equal(t, "build-complete", a.Name())
+       assert.True(t, a.CanHandle(it))
+       handledIt, err := a.Handle(context.TODO(), it)
+       require.NoError(t, err)
+       require.NotNil(t, handledIt)
+       assert.Equal(t, v1.IntegrationPhaseBuildComplete, 
handledIt.Status.Phase)
+}
diff --git a/pkg/controller/integration/build_kit.go 
b/pkg/controller/integration/build_kit.go
index 5bafcc184..8a4006e3d 100644
--- a/pkg/controller/integration/build_kit.go
+++ b/pkg/controller/integration/build_kit.go
@@ -141,12 +141,13 @@ kits:
        }
 
        if integrationKit != nil {
-               action.L.Debug("Setting integration kit for integration", 
"integration", integration.Name, "namespace", integration.Namespace, 
"integration kit", integrationKit.Name)
+               action.L.Debug("Setting integration kit for integration", 
"integration", integration.Name,
+                       "namespace", integration.Namespace, "integration kit", 
integrationKit.Name)
                // Set the kit name so the next handle loop, will fall through 
the
                // same path as integration with a user defined kit
                integration.SetIntegrationKit(integrationKit)
                if integrationKit.Status.Phase == v1.IntegrationKitPhaseReady {
-                       integration.SetBuildOrDeploymentPhase()
+                       integration.SetBuildCompletePhase()
                }
        } else {
                action.L.Debug("Not yet able to assign an integration kit to 
integration",
@@ -203,7 +204,7 @@ func (action *buildKitAction) checkIntegrationKit(ctx 
context.Context, integrati
        }
 
        if kit.Status.Phase == v1.IntegrationKitPhaseReady {
-               integration.SetBuildOrDeploymentPhase()
+               integration.SetBuildCompletePhase()
                integration.SetIntegrationKit(kit)
 
                return integration, nil
diff --git a/pkg/controller/integration/build_kit_test.go 
b/pkg/controller/integration/build_kit_test.go
index 06500aa44..1253a149a 100644
--- a/pkg/controller/integration/build_kit_test.go
+++ b/pkg/controller/integration/build_kit_test.go
@@ -125,7 +125,7 @@ func TestCamelBuildKitKitSetOnIntegration(t *testing.T) {
        a.InjectClient(c)
        handledIt, err = a.Handle(context.TODO(), it)
        require.NoError(t, err)
-       assert.Equal(t, v1.IntegrationPhaseDeploying, handledIt.Status.Phase)
+       assert.Equal(t, v1.IntegrationPhaseBuildComplete, 
handledIt.Status.Phase)
 
        // Move IntegrationKit phase to ready status
        it.Status.Phase = v1.IntegrationPhaseBuildingKit
@@ -243,7 +243,7 @@ func TestCamelBuildKitKitLookupExistingKit(t *testing.T) {
        handledIt, err = a.Handle(context.TODO(), it)
        require.NoError(t, err)
        assert.NotNil(t, handledIt)
-       assert.Equal(t, v1.IntegrationPhaseDeploying, handledIt.Status.Phase)
+       assert.Equal(t, v1.IntegrationPhaseBuildComplete, 
handledIt.Status.Phase)
        assert.Equal(t, ik.Name, it.Status.IntegrationKit.Name)
        assert.Equal(t, ik.Namespace, it.Status.IntegrationKit.Namespace)
        // Found a matching kit (error)
diff --git a/pkg/controller/integration/build_test.go 
b/pkg/controller/integration/build_test.go
index 20c6b36b7..81bd808ad 100644
--- a/pkg/controller/integration/build_test.go
+++ b/pkg/controller/integration/build_test.go
@@ -204,7 +204,7 @@ func TestIntegrationBuildRunningBuildSucceeded(t 
*testing.T) {
        handledIt, err := a.Handle(context.TODO(), it)
        require.NoError(t, err)
        require.NotNil(t, handledIt)
-       assert.Equal(t, v1.IntegrationPhaseDeploying, handledIt.Status.Phase)
+       assert.Equal(t, v1.IntegrationPhaseBuildComplete, 
handledIt.Status.Phase)
        assert.Equal(t, "my-image@123456", handledIt.Status.Image)
        assert.Equal(t, "/deploy/my.jar", handledIt.Status.Jar)
 }
diff --git a/pkg/controller/integration/initialize.go 
b/pkg/controller/integration/initialize.go
index 0fdbd6579..275f97d0e 100644
--- a/pkg/controller/integration/initialize.go
+++ b/pkg/controller/integration/initialize.go
@@ -47,7 +47,8 @@ func (action *initializeAction) Name() string {
 
 // CanHandle tells whether this action can handle the integration.
 func (action *initializeAction) CanHandle(integration *v1.Integration) bool {
-       return integration.Status.Phase == v1.IntegrationPhaseInitialization
+       return integration.Status.Phase == v1.IntegrationPhaseInitialization ||
+               integration.Status.Phase == v1.IntegrationPhaseUnDeploying
 }
 
 // Handle handles the integrations.
@@ -58,12 +59,19 @@ func (action *initializeAction) Handle(ctx context.Context, 
integration *v1.Inte
                return action.importFromExternalApp(integration)
        }
 
-       if integration.Spec.Git != nil {
+       // Only move to the build submitted when we're initializing, never on 
undeploying
+       if integration.Status.Phase == v1.IntegrationPhaseInitialization && 
integration.Spec.Git != nil {
                integration.Status.Phase = v1.IntegrationPhaseBuildSubmitted
 
                return integration, nil
        }
 
+       // We must clear deployment conditions when undeploying.
+       if integration.Status.Phase == v1.IntegrationPhaseUnDeploying {
+               integration.Status.Replicas = nil
+               integration.Status.RemoveCondition(v1.IntegrationConditionReady)
+       }
+
        if _, err := trait.Apply(ctx, action.client, integration, nil); err != 
nil {
                integration.Status.Phase = v1.IntegrationPhaseError
                integration.SetReadyCondition(corev1.ConditionFalse,
@@ -72,12 +80,6 @@ func (action *initializeAction) Handle(ctx context.Context, 
integration *v1.Inte
                return integration, err
        }
 
-       if integration.Status.Image != "" {
-               integration.SetBuildOrDeploymentPhase()
-
-               return integration, nil
-       }
-
        if integration.Status.IntegrationKit == nil {
                ikt, err := action.lookupIntegrationKit(ctx, integration)
                if err != nil {
@@ -87,6 +89,12 @@ func (action *initializeAction) Handle(ctx context.Context, 
integration *v1.Inte
                integration.SetIntegrationKit(ikt)
        }
 
+       if integration.Status.Image != "" {
+               integration.SetBuildCompletePhase()
+
+               return integration, nil
+       }
+
        integration.Status.Phase = v1.IntegrationPhaseBuildingKit
        integration.Status.Version = defaults.Version
        if timestamp := integration.Status.InitializationTimestamp; timestamp 
== nil || timestamp.IsZero() {
diff --git a/pkg/controller/integration/integration_controller.go 
b/pkg/controller/integration/integration_controller.go
index 56f9115b6..14f7a6751 100644
--- a/pkg/controller/integration/integration_controller.go
+++ b/pkg/controller/integration/integration_controller.go
@@ -505,6 +505,7 @@ func (r *reconcileIntegration) Reconcile(ctx 
context.Context, request reconcile.
                NewInitializeAction(),
                NewBuildAction(),
                newBuildKitAction(),
+               NewBuildCompleteAction(),
        }
 
        if instance.IsSynthetic() {
diff --git a/pkg/controller/integration/monitor.go 
b/pkg/controller/integration/monitor.go
index e88d90af3..890c66cb3 100644
--- a/pkg/controller/integration/monitor.go
+++ b/pkg/controller/integration/monitor.go
@@ -63,8 +63,7 @@ func (action *monitorAction) Name() string {
 func (action *monitorAction) CanHandle(integration *v1.Integration) bool {
        return integration.Status.Phase == v1.IntegrationPhaseDeploying ||
                integration.Status.Phase == v1.IntegrationPhaseRunning ||
-               integration.Status.Phase == v1.IntegrationPhaseError ||
-               integration.Status.Phase == v1.IntegrationPhaseBuildComplete
+               integration.Status.Phase == v1.IntegrationPhaseError
 }
 
 //nolint:nestif
@@ -156,15 +155,6 @@ func (action *monitorAction) Handle(ctx context.Context, 
integration *v1.Integra
        }
        action.checkTraitAnnotationsDeprecatedNotice(integration)
 
-       if integration.Status.Phase == v1.IntegrationPhaseBuildComplete {
-               // The following status fields are only filled during execution.
-               // We must remove them to clear any previous execution status.
-               integration.Status.Replicas = nil
-               integration.Status.RemoveCondition(v1.IntegrationConditionReady)
-
-               return integration, nil
-       }
-
        return action.monitorPods(ctx, environment, integration)
 }
 
diff --git a/pkg/trait/gc.go b/pkg/trait/gc.go
index 0b3e64d9e..ca4a9a1f6 100644
--- a/pkg/trait/gc.go
+++ b/pkg/trait/gc.go
@@ -115,23 +115,14 @@ func (t *gcTrait) Configure(e *Environment) (bool, 
*TraitCondition, error) {
 
        // We need to execute this trait only when all resources have been 
created and
        // deployed with a new generation if there is was any change during the 
Integration drift.
-       return e.IntegrationInRunningPhases() || 
e.IntegrationInPhase(v1.IntegrationPhaseBuildComplete), nil, nil
+       return e.IntegrationInRunningPhases() || 
e.IntegrationInPhase(v1.IntegrationPhaseUnDeploying), nil, nil
 }
 
 func (t *gcTrait) Apply(e *Environment) error {
        // Garbage collection runs when:
        // 1. Generation > 1: resource was updated, clean up old generation 
resources
-       // 2. BuildComplete phase AND integration has previously been deployed: 
undeploy scenario
-       shouldRunGC := e.Integration.GetGeneration() > 1
-
-       if !shouldRunGC && 
e.IntegrationInPhase(v1.IntegrationPhaseBuildComplete) {
-               // Only run GC if integration was previously deployed (undeploy 
case)
-               if !hasNeverDeployed(e.Integration) {
-                       shouldRunGC = true
-               }
-       }
-
-       if shouldRunGC {
+       // 2. undeploy scenario
+       if e.Integration.GetGeneration() > 1 || 
e.IntegrationInPhase(v1.IntegrationPhaseUnDeploying) {
                // Register a post action that deletes the existing resources 
that are labelled
                // with the previous integration generation(s).
                // We make the assumption generation is a monotonically 
increasing strictly positive integer,
@@ -204,8 +195,7 @@ func (t *gcTrait) garbageCollectResources(e *Environment) 
error {
 
        // On undeploy, delete all resources regardless of generation.
        // On generation upgrade, filter to only delete old resources.
-       isUndeploying := e.IntegrationInPhase(v1.IntegrationPhaseBuildComplete) 
&& !hasNeverDeployed(e.Integration)
-       if !isUndeploying {
+       if !e.IntegrationInPhase(v1.IntegrationPhaseUnDeploying) {
                selector = selector.Add(*generation)
        }
 
@@ -265,23 +255,6 @@ func canBeDeleted(it *v1.Integration, u 
unstructured.Unstructured) bool {
        return false
 }
 
-// hasNeverDeployed returns true if the integration has never been deployed.
-// Checks both DeploymentTimestamp and Ready condition for reliability.
-func hasNeverDeployed(integration *v1.Integration) bool {
-       // Primary check: DeploymentTimestamp is set when deployment is 
triggered
-       if integration.Status.DeploymentTimestamp != nil && 
!integration.Status.DeploymentTimestamp.IsZero() {
-               return false // has been deployed
-       }
-
-       // Secondary check: Ready condition becomes true only after successful 
deployment
-       readyCond := 
integration.Status.GetCondition(v1.IntegrationConditionReady)
-       if readyCond != nil && readyCond.FirstTruthyTime != nil && 
!readyCond.FirstTruthyTime.IsZero() {
-               return false
-       }
-
-       return true
-}
-
 // getDeletableTypes returns the list of deletable types resources, inspecting 
the rules for which the operator SA is allowed in the
 // Integration namespace.
 func (t *gcTrait) getDeletableTypes(e *Environment) 
(map[schema.GroupVersionKind]struct{}, error) {
diff --git a/pkg/trait/gc_test.go b/pkg/trait/gc_test.go
index f81d216f6..0b0e16843 100644
--- a/pkg/trait/gc_test.go
+++ b/pkg/trait/gc_test.go
@@ -162,7 +162,7 @@ func 
TestGarbageCollectPreserveResourcesWithSameGeneration(t *testing.T) {
 
 func TestGarbageCollectUndeploying(t *testing.T) {
        gcTrait, environment := createNominalGCTest()
-       environment.Integration.Status.Phase = v1.IntegrationPhaseBuildComplete
+       environment.Integration.Status.Phase = v1.IntegrationPhaseUnDeploying
 
        // Simulate undeploy scenario: DeploymentTimestamp set means 
integration was previously deployed
        now := metav1.Now()
@@ -393,54 +393,6 @@ func TestCanResourceBeDeleted(t *testing.T) {
        assert.True(t, canBeDeleted(it, resThisItOwner))
 }
 
-func TestHasNeverDeployed(t *testing.T) {
-       tests := []struct {
-               name                string
-               deploymentTimestamp *metav1.Time
-               readyCondition      *v1.IntegrationCondition
-               expected            bool
-       }{
-               {
-                       name:                "never deployed - both checks nil",
-                       deploymentTimestamp: nil,
-                       readyCondition:      nil,
-                       expected:            true,
-               },
-               {
-                       name:                "deployed - DeploymentTimestamp 
set",
-                       deploymentTimestamp: ptr.To(metav1.Now()),
-                       readyCondition:      nil,
-                       expected:            false,
-               },
-               {
-                       name:                "deployed - Ready FirstTruthyTime 
set",
-                       deploymentTimestamp: nil,
-                       readyCondition: &v1.IntegrationCondition{
-                               Type:            v1.IntegrationConditionReady,
-                               Status:          corev1.ConditionTrue,
-                               FirstTruthyTime: ptr.To(metav1.Now()),
-                       },
-                       expected: false,
-               },
-       }
-
-       for _, tt := range tests {
-               t.Run(tt.name, func(t *testing.T) {
-                       integration := &v1.Integration{
-                               Status: v1.IntegrationStatus{
-                                       DeploymentTimestamp: 
tt.deploymentTimestamp,
-                               },
-                       }
-                       if tt.readyCondition != nil {
-                               integration.Status.Conditions = 
[]v1.IntegrationCondition{*tt.readyCondition}
-                       }
-
-                       result := hasNeverDeployed(integration)
-                       assert.Equal(t, tt.expected, result)
-               })
-       }
-}
-
 func TestGarbageCollectDryBuildSkipsGC(t *testing.T) {
        gcTrait, environment := createNominalGCTest()
        environment.Integration.Status.Phase = v1.IntegrationPhaseBuildComplete
diff --git a/pkg/trait/gitops.go b/pkg/trait/gitops.go
index 667455e3d..f177f5ec9 100644
--- a/pkg/trait/gitops.go
+++ b/pkg/trait/gitops.go
@@ -59,7 +59,7 @@ func (t *gitOpsTrait) Configure(e *Environment) (bool, 
*TraitCondition, error) {
                return false, nil, nil
        }
 
-       return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, 
v1.IntegrationPhaseBuildComplete), nil, nil
+       return e.IntegrationInPhase(v1.IntegrationPhaseBuildComplete), nil, nil
 }
 
 func (t *gitOpsTrait) Apply(e *Environment) error {
diff --git a/pkg/trait/gitops_test.go b/pkg/trait/gitops_test.go
index beae0563f..7d7c978ee 100644
--- a/pkg/trait/gitops_test.go
+++ b/pkg/trait/gitops_test.go
@@ -42,7 +42,7 @@ func TestGitOpsAddAction(t *testing.T) {
        env := &Environment{
                Integration: &v1.Integration{
                        Status: v1.IntegrationStatus{
-                               Phase: v1.IntegrationPhaseDeploying,
+                               Phase: v1.IntegrationPhaseBuildComplete,
                        },
                },
                PostActions: []func(*Environment) error{},

Reply via email to