chore: Factorise metrics observations
diff --git a/pkg/controller/build/metrics.go b/pkg/controller/build/metrics.go
index 59961cc..2b9950b 100644
--- a/pkg/controller/build/metrics.go
+++ b/pkg/controller/build/metrics.go
@@ -18,11 +18,14 @@
 package build
 
 import (
+	"math"
 	"time"
 
 	"sigs.k8s.io/controller-runtime/pkg/metrics"
 
 	"github.com/prometheus/client_golang/prometheus"
+
+	v1 "github.com/apache/camel-k/pkg/apis/camel/v1"
 )
 
 const buildResultLabel = "result"
@@ -76,3 +79,26 @@
 	// Register custom metrics with the global prometheus registry
 	metrics.Registry.MustRegister(buildAttempts, buildDuration, queueDuration)
 }
+
+func observeBuildResult(build *v1.Build, phase v1.BuildPhase, duration time.Duration) {
+	attempt, attemptMax := getBuildAttemptFor(build)
+
+	if phase == v1.BuildPhaseFailed && attempt >= attemptMax {
+		// The phase will be updated in the recovery action,
+		// so let's account for it right now.
+		phase = v1.BuildPhaseError
+	}
+
+	buildAttempts.WithLabelValues(phase.String()).Observe(float64(attempt))
+	buildDuration.WithLabelValues(phase.String()).Observe(duration.Seconds())
+}
+
+func getBuildAttemptFor(build *v1.Build) (int, int) {
+	attempt := 0
+	attemptMax := math.MaxInt32
+	if failure := build.Status.Failure; failure != nil {
+		attempt += failure.Recovery.Attempt
+		attemptMax = failure.Recovery.AttemptMax
+	}
+	return attempt, attemptMax
+}
diff --git a/pkg/controller/build/monitor_pod.go b/pkg/controller/build/monitor_pod.go
index a24673f..10ee4b1 100644
--- a/pkg/controller/build/monitor_pod.go
+++ b/pkg/controller/build/monitor_pod.go
@@ -71,12 +71,7 @@
 		build.Status.Duration = duration.String()
 
 		// Account for the Build metrics
-		buildAttempts.
-			WithLabelValues(build.Status.Phase.String()).
-			Observe(float64(getBuildAttemptsFor(build)))
-		buildDuration.
-			WithLabelValues(build.Status.Phase.String()).
-			Observe(duration.Seconds())
+		observeBuildResult(build, build.Status.Phase, duration)
 
 		for _, task := range build.Spec.Tasks {
 			if task.Image != nil {
@@ -98,12 +93,7 @@
 		build.Status.Duration = duration.String()
 
 		// Account for the Build metrics
-		buildAttempts.
-			WithLabelValues(build.Status.Phase.String()).
-			Observe(float64(getBuildAttemptsFor(build)))
-		buildDuration.
-			WithLabelValues(build.Status.Phase.String()).
-			Observe(duration.Seconds())
+		observeBuildResult(build, build.Status.Phase, duration)
 	}
 
 	return build, nil
diff --git a/pkg/controller/build/recovery.go b/pkg/controller/build/recovery.go
index 5b44662..0c6cc9c 100644
--- a/pkg/controller/build/recovery.go
+++ b/pkg/controller/build/recovery.go
@@ -69,9 +69,6 @@
 
 	if build.Status.Failure.Recovery.Attempt >= build.Status.Failure.Recovery.AttemptMax {
 		build.Status.Phase = v1.BuildPhaseError
-		buildAttempts.
-			WithLabelValues(build.Status.Phase.String()).
-			Observe(float64(getBuildAttemptsFor(build)))
 		return build, nil
 	}
 
@@ -98,11 +95,3 @@
 
 	return build, nil
 }
-
-func getBuildAttemptsFor(build *v1.Build) int {
-	attempts := 1
-	if build.Status.Failure != nil {
-		attempts += build.Status.Failure.Recovery.Attempt
-	}
-	return attempts
-}
diff --git a/pkg/controller/build/schedule_routine.go b/pkg/controller/build/schedule_routine.go
index 2a87b20..56d0c6f 100644
--- a/pkg/controller/build/schedule_routine.go
+++ b/pkg/controller/build/schedule_routine.go
@@ -130,12 +130,7 @@
 			}
 
 			// Account for the Build metrics
-			buildAttempts.
-				WithLabelValues(status.Phase.String()).
-				Observe(float64(getBuildAttemptsFor(build)))
-			buildDuration.
-				WithLabelValues(status.Phase.String()).
-				Observe(duration.Seconds())
+			observeBuildResult(build, status.Phase, duration)
 
 			_ = action.updateBuildStatus(ctx, build, status)
 			break
@@ -152,12 +147,7 @@
 			status.Duration = duration.String()
 
 			// Account for the Build metrics
-			buildAttempts.
-				WithLabelValues(status.Phase.String()).
-				Observe(float64(getBuildAttemptsFor(build)))
-			buildDuration.
-				WithLabelValues(status.Phase.String()).
-				Observe(duration.Seconds())
+			observeBuildResult(build, status.Phase, duration)
 		}
 
 		err := action.updateBuildStatus(ctx, build, status)