Repository: argoproj-labs/argocd-image-updater

Issue:
forceUpdate: true clears Helm parameters when there is no tag in registry, causing invalid image reference
**Describe the bug**

When `forceUpdate: true` is set in the ImageUpdater CRD, argocd-image-updater sets Helm parameter (`global.image.tag`) to empty when there is no image tag specified in the Artifact Registry's `imageName` field. This causes a syntax error because the resulting image reference becomes invalid (e.g., `registry/image:` with an empty tag). Additionally, argocd-image-updater removes the `global.image.tag` Helm parameter that was explicitly set in the ArgoCD Application manifest.

Previously, the image updater would preserve the existing `global.image.tag` value even when there was no specified image tag in Artifact Registry.

**To Reproduce**

1. Create an ArgoCD Application manifest with `global.image.tag` set as a Helm parameter:
   ```yaml
   apiVersion: argoproj.io/v1alpha1
   kind: Application
   metadata:
     name: example-app
   spec:
     project: preview
     source:
       repoURL: https://github.com/org/repo.git
       path: infrastructure/helm/frontend
       targetRevision: test
       helm:
         valueFiles:
           - ../envs/frontend-partners/values-preview.yaml
         parameters:
           - name: global.image.tag
             value: "mq@sha256:123456"
             forceString: true
   ```

2. Create an ImageUpdater CRD with `forceUpdate: true` and an `imageName` that doesn't include a tag:
   ```yaml
   apiVersion: argocd-image-updater.argoproj.io/v1alpha1
   kind: ImageUpdater
   metadata:
     name: example-image-updater
   spec:
     namespace: argocd
     commonUpdateSettings:
       forceUpdate: true
       updateStrategy: "digest"
     writeBackConfig:
       method: "argocd"
     applicationRefs:
       - namePattern: "example-app"
         images:
           - alias: frontend-partners
             imageName: "example-registry/docker/frontend-partners"
             manifestTargets:
               helm:
                 name: "global.image.name"
                 tag: "global.image.tag"
   ```

3. Apply both resources to the cluster
4. Observe that argocd-image-updater removes the `global.image.tag` parameter from the Application manifest
5. Observe the error when ArgoCD tries to deploy the application

**Expected behavior**

The image updater should preserve the existing `global.image.tag` value when:
- `forceUpdate: true` is set
- The `global.image.tag` is explicitly set in the Application manifest as a Helm parameter

The image updater should not remove Helm parameters from the Application manifest, and should not set `global.image.tag` to empty, which results in invalid image references.

**Additional context**

- This issue only occurs when `forceUpdate: true` is set
- When `forceUpdate: false` (or omitted), the image updater works correctly and preserves the tag value
- This appears to be a regression from previous behavior where the tag value was preserved
- Related to the fix for issue #1344
- The error occurs because Kubernetes/Docker cannot parse image references with empty tags (e.g., `registry/image:`)

**Version**

1.0.1

**Logs**

```
Failed to apply default image tag "example-registry/docker/frontend-partners:": couldn't parse image name "example-registry/docker/frontend-partners:": invalid reference format
```



Discussion:
@chengfang: I agree that the original helm param should not be overwritten with empty value or removed. But the `digest` update strategy expects a tag when you specify the imageName. In your case, `imageName: "example-registry/docker/frontend-partners"
` should be something like `imageName: "example-registry/docker/frontend-partners:latest"
` for digest strategy to work properly.

Solution patch:
diff --git a/pkg/argocd/argocd.go b/pkg/argocd/argocd.go
index 629d83a..862b710 100644
--- a/pkg/argocd/argocd.go
+++ b/pkg/argocd/argocd.go
@@ -501,9 +501,14 @@ func SetHelmImage(app *v1alpha1.Application, newImage *image.ContainerImage) err
 			p := v1alpha1.HelmParameter{Name: hpImageName, Value: newImage.GetFullNameWithoutTag(), ForceString: true}
 			mergeParams = append(mergeParams, p)
 		}
+		// Only set the tag parameter if we have a non-empty tag value.
+		// When forceUpdate is enabled and no tag is specified, the tag can be empty.
+		// Setting an empty tag would overwrite existing tag values and cause invalid image references.
 		if hpImageTag != "" {
-			p := v1alpha1.HelmParameter{Name: hpImageTag, Value: newImage.GetTagWithDigest(), ForceString: true}
-			mergeParams = append(mergeParams, p)
+			if tagValue := newImage.GetTagWithDigest(); tagValue != "" {
+				p := v1alpha1.HelmParameter{Name: hpImageTag, Value: tagValue, ForceString: true}
+				mergeParams = append(mergeParams, p)
+			}
 		}
 	}
 



Test patch:
diff --git a/pkg/argocd/argocd_test.go b/pkg/argocd/argocd_test.go
index 6caf429..7d591fc 100644
--- a/pkg/argocd/argocd_test.go
+++ b/pkg/argocd/argocd_test.go
@@ -1008,6 +1008,79 @@ func Test_SetHelmImage(t *testing.T) {
 		require.Error(t, err)
 	})
 
+	t.Run("Test set Helm image with empty tag preserves existing tag (issue #1351)", func(t *testing.T) {
+		// This test verifies the fix for issue #1351:
+		// https://github.com/argoproj-labs/argocd-image-updater/issues/1351
+		//
+		// Scenario from the issue:
+		// - User has a Helm application with global.image.tag set to "mq@sha256:123456"
+		// - Application annotation specifies:
+		//   - imageName: "example-registry/docker/frontend-partners" (NO TAG!)
+		//   - forceUpdate: true
+		//   - updateStrategy: digest
+		// - Bug: SetHelmImage was setting global.image.tag to empty string "",
+		//   causing "invalid reference format" error when parsing "example-registry/docker/frontend-partners:"
+		// - Fix: When the new image has no tag, preserve the existing tag parameter value
+		//
+		// This test simulates the SetHelmImage call with an image that has no tag
+		// and verifies the existing tag parameter is NOT overwritten with empty string.
+		app := &v1alpha1.Application{
+			ObjectMeta: v1.ObjectMeta{
+				Name:      "frontend-partners",
+				Namespace: "argocd",
+				Annotations: map[string]string{
+					fmt.Sprintf(registryCommon.Prefixed(common.ImageUpdaterAnnotationPrefix, registryCommon.HelmParamImageNameAnnotationSuffix), "frontend-partners"): "global.image.name",
+					fmt.Sprintf(registryCommon.Prefixed(common.ImageUpdaterAnnotationPrefix, registryCommon.HelmParamImageTagAnnotationSuffix), "frontend-partners"):  "global.image.tag",
+				},
+			},
+			Spec: v1alpha1.ApplicationSpec{
+				Source: &v1alpha1.ApplicationSource{
+					Helm: &v1alpha1.ApplicationSourceHelm{
+						Parameters: []v1alpha1.HelmParameter{
+							{
+								Name:  "global.image.tag",
+								Value: "mq@sha256:123456", // Existing tag from the issue
+							},
+							{
+								Name:  "global.image.name",
+								Value: "example-registry/docker/frontend-partners",
+							},
+						},
+					},
+				},
+			},
+			Status: v1alpha1.ApplicationStatus{
+				SourceType: v1alpha1.ApplicationSourceTypeHelm,
+				Summary: v1alpha1.ApplicationSummary{
+					// Empty - simulating a Job that doesn't appear in app status
+					Images: []string{},
+				},
+			},
+		}
+
+		// Create an image WITHOUT a tag - exactly as in the issue
+		// User specified: imageName: "example-registry/docker/frontend-partners"
+		img := image.NewFromIdentifier("frontend-partners=example-registry/docker/frontend-partners")
+
+		err := SetHelmImage(app, img)
+		require.NoError(t, err)
+		require.NotNil(t, app.Spec.Source.Helm)
+
+		// Find the tag parameter - it should still have the original value
+		var tagParam *v1alpha1.HelmParameter
+		for i, p := range app.Spec.Source.Helm.Parameters {
+			if p.Name == "global.image.tag" {
+				tagParam = &app.Spec.Source.Helm.Parameters[i]
+				break
+			}
+		}
+
+		// CRITICAL: The tag parameter should still exist with its original value "mq@sha256:123456"
+		// Before the fix, this would be "" causing "invalid reference format" error
+		require.NotNil(t, tagParam, "Tag parameter should be preserved")
+		assert.Equal(t, "mq@sha256:123456", tagParam.Value, "Existing tag value should not be overwritten with empty string")
+	})
+
 }
 
 func TestKubernetesClient(t *testing.T) {