Repository: cloudnative-pg/cloudnative-pg

Issue:
[Bug]:  Renovate should use pinDigest
### Is there an existing issue already for this bug?

- [x] I have searched for an existing issue, and could not find anything. I believe this is a new bug.

### I have read the troubleshooting guide

- [x] I have read the troubleshooting guide and I think this is a new bug.

### I am running a supported version of CloudNativePG

- [x] I have read the troubleshooting guide and I think this is a new bug.

### Contact Details

_No response_

### Version

1.25 (latest patch)

### What version of Kubernetes are you using?

1.32

### What is your Kubernetes environment?

Self-managed: kind (evaluation)

### How did you install the operator?

YAML manifest

### What happened?

Following the OSSF recommendations we should pin the version using the digest on our workflows 

### Cluster resource

```shell

```

### Relevant log output

```shell

```

### Code of Conduct

- [x] I agree to follow this project's Code of Conduct

Discussion:

Solution patch:
diff --git a/.github/renovate.json5 b/.github/renovate.json5
index d2eb5117..601d4502 100644
--- a/.github/renovate.json5
+++ b/.github/renovate.json5
@@ -20,6 +20,7 @@
     'contribute/**',
     'licenses/**',
     'pkg/versions/**',
+    'pkg/specs/pgbouncer/',
   ],
   postUpdateOptions: [
     'gomodTidy',
diff --git a/internal/controller/cluster_image.go b/internal/controller/cluster_image.go
index d838adb7..1df58d3b 100644
--- a/internal/controller/cluster_image.go
+++ b/internal/controller/cluster_image.go
@@ -52,10 +52,7 @@ func (r *ClusterReconciler) reconcileImage(ctx context.Context, cluster *apiv1.C
 		return &ctrl.Result{}, r.RegisterPhase(ctx, cluster, apiv1.PhaseImageCatalogError, err.Error())
 	}
 
-	currentDataImage := cluster.Status.Image
-	if cluster.Status.MajorVersionUpgradeFromImage != nil {
-		currentDataImage = *cluster.Status.MajorVersionUpgradeFromImage
-	}
+	currentDataImage := getCurrentPgDataImage(&cluster.Status)
 
 	// Case 1: the cluster is being initialized and there is still no
 	// running image. In this case, we should simply apply the image selected by the user.
@@ -72,7 +69,6 @@ func (r *ClusterReconciler) reconcileImage(ctx context.Context, cluster *apiv1.C
 	// Case 2: there's a running image. The code checks if the user selected
 	// an image of the same major version or if a change in the major
 	// version has been requested.
-	var majorVersionUpgradeFromImage *string
 	currentVersion, err := version.FromTag(reference.New(currentDataImage).Tag)
 	if err != nil {
 		contextLogger.Error(err, "While parsing current major versions")
@@ -85,6 +81,7 @@ func (r *ClusterReconciler) reconcileImage(ctx context.Context, cluster *apiv1.C
 		return nil, err
 	}
 
+	var majorVersionUpgradeFromImage *string
 	switch {
 	case currentVersion.Major() < requestedVersion.Major():
 		// The current major version is older than the requested one
@@ -109,6 +106,18 @@ func (r *ClusterReconciler) reconcileImage(ctx context.Context, cluster *apiv1.C
 	)
 }
 
+// getCurrentPgDataImage returns Postgres image that was able to run the cluster
+// PGDATA correctly last time.
+// This is important in the context of major upgrade because it contains the
+// image with the "old" major version even when there are no Pods available.
+func getCurrentPgDataImage(status *apiv1.ClusterStatus) string {
+	if status.MajorVersionUpgradeFromImage != nil {
+		return *status.MajorVersionUpgradeFromImage
+	}
+
+	return status.Image
+}
+
 func (r *ClusterReconciler) getConfiguredImage(ctx context.Context, cluster *apiv1.Cluster) (string, error) {
 	contextLogger := log.FromContext(ctx)
 



Test patch:
diff --git a/internal/controller/cluster_image_test.go b/internal/controller/cluster_image_test.go
index ec69797b..e76c58e9 100644
--- a/internal/controller/cluster_image_test.go
+++ b/internal/controller/cluster_image_test.go
@@ -25,6 +25,7 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/tools/record"
+	"k8s.io/utils/ptr"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 
 	apiv1 "github.com/cloudnative-pg/cloudnative-pg/api/v1"
@@ -212,3 +213,22 @@ var _ = Describe("Cluster image detection", func() {
 		Expect(*cluster.Status.MajorVersionUpgradeFromImage).To(Equal("postgres:16.2"))
 	})
 })
+
+var _ = Describe("Major version tracking with getCurrentPgDataImage", func() {
+	It("returns the current major version if no major version update has been requested", func() {
+		status := &apiv1.ClusterStatus{
+			Image: "postgres:15.2",
+		}
+
+		Expect(getCurrentPgDataImage(status)).To(Equal("postgres:15.2"))
+	})
+
+	It("returns the old major version if a major version update has been requested", func() {
+		status := &apiv1.ClusterStatus{
+			Image:                        "postgres:15.2",
+			MajorVersionUpgradeFromImage: ptr.To("postgres:14.3"),
+		}
+
+		Expect(getCurrentPgDataImage(status)).To(Equal("postgres:14.3"))
+	})
+})