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