Repository: cloudnative-pg/cloudnative-pg

Issue:
[Bug]: Cluster.cluster.cnpg.io XXX is invalid: spec.imageName: Invalid value: ""
### 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

adam@gelwarg.net

### Version

1.26.0-rc1

### What version of Kubernetes are you using?

1.30

### What is your Kubernetes environment?

Cloud: Amazon EKS

### How did you install the operator?

Helm

### What happened?

Deploying a new cluster with 1.26.0-rc1 & rc2 fails and appears to reference an invalid `.spec.imageName` when only an `imageCatalogRef` is provided.

### Cluster resource

```shell
---
apiVersion: postgresql.cnpg.io/v1
kind: ClusterImageCatalog
metadata:
  name: mycatalog
spec:
  images:
  - major: 17
    image: docker.io/timescale/timescaledb:2.18.2-pg17
---
apiVersion: postgresql.cnpg.io/v1
kind: Cluster
metadata:
  name: mytest
spec:
  instances: 1
  imageCatalogRef:
    apiGroup: postgresql.cnpg.io
    kind: ClusterImageCatalog
    major: 17
    name: mycatalog
  storage:
    size: 4Gi
  postgresUID: 70
  postgresGID: 70
```

### Relevant log output

```shell
error:
  admission webhook "vcluster.cnpg.io" denied the request: Cluster.cluster.cnpg.io "mytest" is invalid: spec.imageName: Invalid value: "": Unsupported PostgreSQL version. Versions 12 or newer are supported

stacktrace:
  sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).reconcileHandler
        pkg/mod/sigs.k8s.io/controller-runtime@v0.20.4/pkg/internal/controller/controller.go:347
  sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).processNextWorkItem
        pkg/mod/sigs.k8s.io/controller-runtime@v0.20.4/pkg/internal/controller/controller.go:294
  sigs.k8s.io/controller-runtime/pkg/internal/controller.(*Controller[...]).Start.func2.2
        pkg/mod/sigs.k8s.io/controller-runtime@v0.20.4/pkg/internal/controller/controller.go:255
```

### Code of Conduct

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

Hints from issue comments:
@dosubot: <!-- Greeting -->
Hello @agelwarg! I'm [Dosu](https://dosu.dev), your friendly bot here to help you with bugs, questions, and contributions while you wait for a human maintainer. Let's get to the bottom of this issue together! 🚀

<!-- Answer -->
There is a similar closed issue related to the `imageCatalogRef` and `imageName` fields. The issue was about the CNPG Plugin displaying the wrong image version of a POD when using `ImageCatalogRef` <sup>[[1]](https://github.com/cloudnative-pg/cloudnative-pg/issues/4666)</sup>. However, no solution was found in the comments of that issue.

Regarding your specific problem, deploying a new cluster with CloudNativePG version 1.26.0-rc1 & rc2 on Amazon EKS fails with an invalid `.spec.imageName` error when only an `imageCatalogRef` is provided. This issue might be due to the `imageCatalogRef` requiring specific properties to be set, including `kind`, `major`, and `name`. The error suggests that the `imageName` field is expected but not provided, indicating that the configuration might be missing required fields or incorrectly set.

Ensure that the `imageCatalogRef` is correctly configured with all required fields and matches the expected `kind` and `apiGroup`. Additionally, verify that the Helm chart values are correctly set to provide either a custom `imageName` or the necessary `type` and `version` values to construct a default image name. If the `imageName` is still required, it might be due to a misconfiguration or a missing default value in the deployment setup <sup>[[2]](https://github.com/cloudnative-pg/cloudnative-pg/blob/main/releases/cnpg-1.26.0-rc2.yaml)</sup><sup>[[3]](https://github.com/cloudnative-pg/charts/blob/main/charts/cluster/templates/_helpers.tpl)</sup>.


<!-- Dosu Comment Footer -->
*To continue talking to [Dosu](https://dosu.dev), mention @dosu.*

---
<sup>Help Dosu learn! Give it feedback: [Great Response](https://app.dosu.dev/response-feedback/95db96cb-042d-4665-a5fa-e2ec04589746?feedback_type=great_response) | [Irrelevant Answer](https://app.dosu.dev/response-feedback/95db96cb-042d-4665-a5fa-e2ec04589746?feedback_type=irrelevant_answer) | [Incorrect Sources](https://app.dosu.dev/response-feedback/95db96cb-042d-4665-a5fa-e2ec04589746?feedback_type=incorrect_sources) | [Too Verbose](https://app.dosu.dev/response-feedback/95db96cb-042d-4665-a5fa-e2ec04589746?feedback_type=too_verbose) | [Hallucination](https://app.dosu.dev/response-feedback/95db96cb-042d-4665-a5fa-e2ec04589746?feedback_type=hallucination) | [Bug Report](https://app.dosu.dev/response-feedback/95db96cb-042d-4665-a5fa-e2ec04589746?feedback_type=bug_report) | [Other](https://app.dosu.dev/response-feedback/95db96cb-042d-4665-a5fa-e2ec04589746?feedback_type=other)</sup>


Solution patch:
diff --git a/api/v1/cluster_funcs.go b/api/v1/cluster_funcs.go
index c2f27f7b..1be7def3 100644
--- a/api/v1/cluster_funcs.go
+++ b/api/v1/cluster_funcs.go
@@ -402,6 +402,10 @@ func (cluster *Cluster) SetInContext(ctx context.Context) context.Context {
 // ghcr.io/cloudnative-pg/postgresql:14.0 corresponds to version (14,0)
 // ghcr.io/cloudnative-pg/postgresql:13.2 corresponds to version (13,2)
 func (cluster *Cluster) GetPostgresqlVersion() (version.Data, error) {
+	if cluster.Spec.ImageCatalogRef != nil {
+		return version.FromTag(strconv.Itoa(cluster.Spec.ImageCatalogRef.Major))
+	}
+
 	if cluster.Status.Image != "" {
 		return version.FromTag(reference.New(cluster.Status.Image).Tag)
 	}
@@ -410,10 +414,6 @@ func (cluster *Cluster) GetPostgresqlVersion() (version.Data, error) {
 		return version.FromTag(reference.New(cluster.Spec.ImageName).Tag)
 	}
 
-	if cluster.Spec.ImageCatalogRef != nil {
-		return version.FromTag(strconv.Itoa(cluster.Spec.ImageCatalogRef.Major))
-	}
-
 	// Fallback for unit tests where a cluster is created without status or defaults
 	return version.FromTag(reference.New(configuration.Current.PostgresImageName).Tag)
 }



Test patch:
diff --git a/api/v1/cluster_funcs_test.go b/api/v1/cluster_funcs_test.go
index a9607b05..a229d2ff 100644
--- a/api/v1/cluster_funcs_test.go
+++ b/api/v1/cluster_funcs_test.go
@@ -809,6 +809,35 @@ var _ = Describe("PostgreSQL version detection", func() {
 		}
 		Expect(cluster.GetPostgresqlVersion()).To(Equal(version.New(16, 0)))
 	})
+
+	It("correctly prioritizes ImageCatalogRef over Status.Image and Spec.ImageName", func() {
+		cluster := Cluster{
+			Spec: ClusterSpec{
+				ImageName: "ghcr.io/cloudnative-pg/postgresql:14.1",
+				ImageCatalogRef: &ImageCatalogRef{
+					TypedLocalObjectReference: corev1.TypedLocalObjectReference{
+						Name: "test-catalog",
+						Kind: "ImageCatalog",
+					},
+					Major: 16,
+				},
+			},
+			Status: ClusterStatus{
+				Image: "ghcr.io/cloudnative-pg/postgresql:15.2",
+			},
+		}
+
+		// ImageCatalogRef should take precedence
+		Expect(cluster.GetPostgresqlVersion()).To(Equal(version.New(16, 0)))
+
+		// Remove ImageCatalogRef, Status.Image should take precedence
+		cluster.Spec.ImageCatalogRef = nil
+		Expect(cluster.GetPostgresqlVersion()).To(Equal(version.New(15, 2)))
+
+		// Remove Status.Image, Spec.ImageName should be used
+		cluster.Status.Image = ""
+		Expect(cluster.GetPostgresqlVersion()).To(Equal(version.New(14, 1)))
+	})
 })
 
 var _ = Describe("Default Metrics", func() {