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 5f48a01f9 feat(trait): enable GitOps with dry build
5f48a01f9 is described below

commit 5f48a01f96d82b900c2ce321233e45889b8aaecf
Author: Pasquale Congiusti <[email protected]>
AuthorDate: Sat Mar 7 11:25:12 2026 +0100

    feat(trait): enable GitOps with dry build
    
    Closes #6420
---
 docs/modules/ROOT/partials/apis/camel-k-crds.adoc  |  7 +++++++
 helm/camel-k/crds/camel-k-crds.yaml                |  5 +++++
 pkg/apis/camel/v1/integration_types.go             |  2 ++
 pkg/apis/camel/v1/integration_types_support.go     | 12 ++++++++++++
 pkg/apis/camel/v1/zz_generated.deepcopy.go         |  4 ++++
 .../camel/v1/integrationstatus.go                  | 10 ++++++++++
 pkg/controller/integration/build.go                |  8 +-------
 pkg/controller/integration/build_kit.go            | 18 ++----------------
 pkg/controller/integration/initialize.go           |  8 +-------
 .../crd/bases/camel.apache.org_integrations.yaml   |  5 +++++
 pkg/trait/gitops.go                                | 22 +++++++++++++++++-----
 pkg/trait/gitops_test.go                           |  7 ++++---
 12 files changed, 70 insertions(+), 38 deletions(-)

diff --git a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc 
b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
index ff0475873..0b2c1f4c4 100644
--- a/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
+++ b/docs/modules/ROOT/partials/apis/camel-k-crds.adoc
@@ -3735,6 +3735,13 @@ the timestamp representing the last time when this 
integration was initialized.
 
 the timestamp representing the last time when this integration was deployed.
 
+|`lastBuildTimestamp` +
+*https://kubernetes.io/docs/reference/generated/kubernetes-api/v1.35/#time-v1-meta[Kubernetes
 meta/v1.Time]*
+|
+
+
+the timestamp representing the last time when this integration was built.
+
 
 |===
 
diff --git a/helm/camel-k/crds/camel-k-crds.yaml 
b/helm/camel-k/crds/camel-k-crds.yaml
index 384688690..e3d28d264 100644
--- a/helm/camel-k/crds/camel-k-crds.yaml
+++ b/helm/camel-k/crds/camel-k-crds.yaml
@@ -22611,6 +22611,11 @@ spec:
               jar:
                 description: the Java jar dependency to execute (if available)
                 type: string
+              lastBuildTimestamp:
+                description: the timestamp representing the last time when 
this integration
+                  was built.
+                format: date-time
+                type: string
               lastDeploymentTimestamp:
                 description: the timestamp representing the last time when 
this integration
                   was deployed.
diff --git a/pkg/apis/camel/v1/integration_types.go 
b/pkg/apis/camel/v1/integration_types.go
index 4234d74ad..554de5cfb 100644
--- a/pkg/apis/camel/v1/integration_types.go
+++ b/pkg/apis/camel/v1/integration_types.go
@@ -137,6 +137,8 @@ type IntegrationStatus struct {
        InitializationTimestamp *metav1.Time 
`json:"lastInitTimestamp,omitempty"`
        // the timestamp representing the last time when this integration was 
deployed.
        DeploymentTimestamp *metav1.Time 
`json:"lastDeploymentTimestamp,omitempty"`
+       // the timestamp representing the last time when this integration was 
built.
+       BuildTimestamp *metav1.Time `json:"lastBuildTimestamp,omitempty"`
 }
 
 // +kubebuilder:object:root=true
diff --git a/pkg/apis/camel/v1/integration_types_support.go 
b/pkg/apis/camel/v1/integration_types_support.go
index 47c6cf787..2e7fa71c7 100644
--- a/pkg/apis/camel/v1/integration_types_support.go
+++ b/pkg/apis/camel/v1/integration_types_support.go
@@ -370,6 +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() {
+       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
+       }
+}
+
 // GetCondition returns the condition with the provided type.
 func (in *IntegrationStatus) GetCondition(condType IntegrationConditionType) 
*IntegrationCondition {
        for i := range in.Conditions {
diff --git a/pkg/apis/camel/v1/zz_generated.deepcopy.go 
b/pkg/apis/camel/v1/zz_generated.deepcopy.go
index 9b1a09711..edfe45e01 100644
--- a/pkg/apis/camel/v1/zz_generated.deepcopy.go
+++ b/pkg/apis/camel/v1/zz_generated.deepcopy.go
@@ -1915,6 +1915,10 @@ func (in *IntegrationStatus) DeepCopyInto(out 
*IntegrationStatus) {
                in, out := &in.DeploymentTimestamp, &out.DeploymentTimestamp
                *out = (*in).DeepCopy()
        }
+       if in.BuildTimestamp != nil {
+               in, out := &in.BuildTimestamp, &out.BuildTimestamp
+               *out = (*in).DeepCopy()
+       }
 }
 
 // DeepCopy is an autogenerated deepcopy function, copying the receiver, 
creating a new IntegrationStatus.
diff --git a/pkg/client/camel/applyconfiguration/camel/v1/integrationstatus.go 
b/pkg/client/camel/applyconfiguration/camel/v1/integrationstatus.go
index 7358083bb..b97d50978 100644
--- a/pkg/client/camel/applyconfiguration/camel/v1/integrationstatus.go
+++ b/pkg/client/camel/applyconfiguration/camel/v1/integrationstatus.go
@@ -76,6 +76,8 @@ type IntegrationStatusApplyConfiguration struct {
        InitializationTimestamp *metav1.Time 
`json:"lastInitTimestamp,omitempty"`
        // the timestamp representing the last time when this integration was 
deployed.
        DeploymentTimestamp *metav1.Time 
`json:"lastDeploymentTimestamp,omitempty"`
+       // the timestamp representing the last time when this integration was 
built.
+       BuildTimestamp *metav1.Time `json:"lastBuildTimestamp,omitempty"`
 }
 
 // IntegrationStatusApplyConfiguration constructs a declarative configuration 
of the IntegrationStatus type for use with
@@ -278,3 +280,11 @@ func (b *IntegrationStatusApplyConfiguration) 
WithDeploymentTimestamp(value meta
        b.DeploymentTimestamp = &value
        return b
 }
+
+// WithBuildTimestamp sets the BuildTimestamp field in the declarative 
configuration to the given value
+// and returns the receiver, so that objects can be built by chaining "With" 
function invocations.
+// If called multiple times, the BuildTimestamp field is set to the value of 
the last call.
+func (b *IntegrationStatusApplyConfiguration) WithBuildTimestamp(value 
metav1.Time) *IntegrationStatusApplyConfiguration {
+       b.BuildTimestamp = &value
+       return b
+}
diff --git a/pkg/controller/integration/build.go 
b/pkg/controller/integration/build.go
index 2192e7ca3..2d1971de5 100644
--- a/pkg/controller/integration/build.go
+++ b/pkg/controller/integration/build.go
@@ -228,13 +228,7 @@ func (action *buildAction) handleBuildRunning(ctx 
context.Context, it *v1.Integr
                        }
                        it.Status.Image = fmt.Sprintf("%s@%s", image, 
build.Status.Digest)
                }
-               if it.Annotations[v1.IntegrationDontRunAfterBuildAnnotation] == 
v1.IntegrationDontRunAfterBuildAnnotationTrueValue {
-                       it.Status.Phase = v1.IntegrationPhaseBuildComplete
-               } else {
-                       now := metav1.Now().Rfc3339Copy()
-                       it.Status.DeploymentTimestamp = &now
-                       it.Status.Phase = v1.IntegrationPhaseDeploying
-               }
+               it.SetBuildOrDeploymentPhase()
        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_kit.go 
b/pkg/controller/integration/build_kit.go
index 3f18a676d..5bafcc184 100644
--- a/pkg/controller/integration/build_kit.go
+++ b/pkg/controller/integration/build_kit.go
@@ -25,7 +25,6 @@ import (
        "github.com/apache/camel-k/v2/pkg/trait"
        "github.com/apache/camel-k/v2/pkg/util/digest"
        "github.com/apache/camel-k/v2/pkg/util/kubernetes"
-       metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 func newBuildKitAction() Action {
@@ -141,20 +140,13 @@ kits:
                }
        }
 
-       //nolint:nestif
        if integrationKit != nil {
                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 {
-                       if 
integration.Annotations[v1.IntegrationDontRunAfterBuildAnnotation] == 
v1.IntegrationDontRunAfterBuildAnnotationTrueValue {
-                               integration.Status.Phase = 
v1.IntegrationPhaseBuildComplete
-                       } else {
-                               now := metav1.Now().Rfc3339Copy()
-                               integration.Status.DeploymentTimestamp = &now
-                               integration.Status.Phase = 
v1.IntegrationPhaseDeploying
-                       }
+                       integration.SetBuildOrDeploymentPhase()
                }
        } else {
                action.L.Debug("Not yet able to assign an integration kit to 
integration",
@@ -211,13 +203,7 @@ func (action *buildKitAction) checkIntegrationKit(ctx 
context.Context, integrati
        }
 
        if kit.Status.Phase == v1.IntegrationKitPhaseReady {
-               if 
integration.Annotations[v1.IntegrationDontRunAfterBuildAnnotation] == 
v1.IntegrationDontRunAfterBuildAnnotationTrueValue {
-                       integration.Status.Phase = 
v1.IntegrationPhaseBuildComplete
-               } else {
-                       now := metav1.Now().Rfc3339Copy()
-                       integration.Status.DeploymentTimestamp = &now
-                       integration.Status.Phase = v1.IntegrationPhaseDeploying
-               }
+               integration.SetBuildOrDeploymentPhase()
                integration.SetIntegrationKit(kit)
 
                return integration, nil
diff --git a/pkg/controller/integration/initialize.go 
b/pkg/controller/integration/initialize.go
index 65ae414b8..0fdbd6579 100644
--- a/pkg/controller/integration/initialize.go
+++ b/pkg/controller/integration/initialize.go
@@ -73,13 +73,7 @@ func (action *initializeAction) Handle(ctx context.Context, 
integration *v1.Inte
        }
 
        if integration.Status.Image != "" {
-               if 
integration.Annotations[v1.IntegrationDontRunAfterBuildAnnotation] == 
v1.IntegrationDontRunAfterBuildAnnotationTrueValue {
-                       integration.Status.Phase = 
v1.IntegrationPhaseBuildComplete
-               } else {
-                       now := metav1.Now().Rfc3339Copy()
-                       integration.Status.DeploymentTimestamp = &now
-                       integration.Status.Phase = v1.IntegrationPhaseDeploying
-               }
+               integration.SetBuildOrDeploymentPhase()
 
                return integration, nil
        }
diff --git a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml 
b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
index 4b71a819d..f87fb884b 100644
--- a/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
+++ b/pkg/resources/config/crd/bases/camel.apache.org_integrations.yaml
@@ -9515,6 +9515,11 @@ spec:
               jar:
                 description: the Java jar dependency to execute (if available)
                 type: string
+              lastBuildTimestamp:
+                description: the timestamp representing the last time when 
this integration
+                  was built.
+                format: date-time
+                type: string
               lastDeploymentTimestamp:
                 description: the timestamp representing the last time when 
this integration
                   was deployed.
diff --git a/pkg/trait/gitops.go b/pkg/trait/gitops.go
index 936ca930d..667455e3d 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), nil, nil
+       return e.IntegrationInPhase(v1.IntegrationPhaseDeploying, 
v1.IntegrationPhaseBuildComplete), nil, nil
 }
 
 func (t *gitOpsTrait) Apply(e *Environment) error {
@@ -118,10 +118,10 @@ func (t *gitOpsTrait) pushGitOpsItInGitRepo(ctx 
context.Context, it *v1.Integrat
        nowDate := time.Now().Format("20060102-150405")
        branchName := t.BranchPush
        if branchName == "" {
-               // NOTE: this is important to guarantee idempotency. We make 
sure not to create
-               // more than one branch from different reconciliation cycles.
-               branchNameDate := 
it.Status.DeploymentTimestamp.Format("20060102-150405")
-               branchName = "cicd/candidate-release-" + branchNameDate
+               branchName, err = getBranchName(&it.Status)
+               if err != nil {
+                       return err
+               }
        }
        commitMessage := "feat(ci): build completed on " + nowDate
        branchRef := plumbing.NewBranchReferenceName(branchName)
@@ -268,3 +268,15 @@ func (t *gitOpsTrait) getCommitterEmail() string {
 
        return t.CommiterEmail
 }
+
+// getBranchName returns the branch name to use based on the timestamp.
+func getBranchName(status *v1.IntegrationStatus) (string, error) {
+       if status.BuildTimestamp == nil {
+               return "", errors.New("no Build timestamp available in 
Integration status")
+       }
+       // NOTE: this is important to guarantee idempotency. We make sure not 
to create
+       // more than one branch from different reconciliation cycles.
+       branchNameDate := status.BuildTimestamp.Format("20060102-150405")
+
+       return "cicd/candidate-release-" + branchNameDate, nil
+}
diff --git a/pkg/trait/gitops_test.go b/pkg/trait/gitops_test.go
index ebc40a046..beae0563f 100644
--- a/pkg/trait/gitops_test.go
+++ b/pkg/trait/gitops_test.go
@@ -76,6 +76,7 @@ func TestGitOpsPushRepoDefault(t *testing.T) {
        now := metav1.Now().Rfc3339Copy()
        it.Status = v1.IntegrationStatus{
                Image:               "my-img-recently-baked",
+               BuildTimestamp:      &now,
                DeploymentTimestamp: &now,
        }
 
@@ -89,7 +90,7 @@ func TestGitOpsPushRepoDefault(t *testing.T) {
        lastCommitMessage, err := getLastCommitMessage(tmpGitDir)
        require.NoError(t, err)
        assert.Contains(t, lastCommitMessage, "feat(ci): build complete")
-       branchName, err := getBranchName(tmpGitDir)
+       branchName, err := getBranchNameFromDir(tmpGitDir)
        require.NoError(t, err)
        assert.Contains(t, branchName, "cicd/candidate-release")
        remoteUrl, err := getRemoteURL(tmpGitDir)
@@ -157,8 +158,8 @@ func getLastCommitMessage(dirPath string) (string, error) {
        return commit.Message, nil
 }
 
-// getBranchName returns the branch name of a given directory.
-func getBranchName(dirPath string) (string, error) {
+// getBranchNameFromDir returns the branch name of a given directory.
+func getBranchNameFromDir(dirPath string) (string, error) {
        repo, err := git.PlainOpen(dirPath)
        if err != nil {
                return "", err

Reply via email to