Repository: argoproj-labs/argo-rollouts-manager
Pull request #44 (2024-02-27 18:42:38)

Improve upstream Rollouts E2E test parsing, by allowing test retries
**What does this PR do / why we need it**:
- Improve upstream Rollouts E2E test parsing: replace the simple bash-based parsing with a short Go script.
- The previous version of the code would just ignore retries. 
- The new version will allow parsing of retries, and will report a failure only if a test never succeeds (after 5 retries)

**Have you updated the necessary documentation?**
* [ ] Documentation update is required by this PR, and has been updated.

**Which issue(s) this PR fixes**:
N/A




Patch:
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 50cec0b..8b1a159 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -19,10 +19,10 @@ jobs:
     steps:
     - uses: actions/checkout@v3
 
-    - name: Set up Go
-      uses: actions/setup-go@v3
+    - name: Setup Golang
+      uses: actions/setup-go@v5.0.0
       with:
-        go-version: 1.19
+        go-version-file: './go.mod'
 
     - name: Build
       run: make build
diff --git a/.github/workflows/lint.yaml b/.github/workflows/lint.yaml
index 370a969..4efbf81 100644
--- a/.github/workflows/lint.yaml
+++ b/.github/workflows/lint.yaml
@@ -19,10 +19,10 @@ jobs:
       - name: Checkout code
         uses: actions/checkout@v2
 
-      - name: Set up Go
-        uses: actions/setup-go@v3
+      - name: Setup Golang
+        uses: actions/setup-go@v5.0.0
         with:
-          go-version: 'stable'
+          go-version-file: './go.mod'          
 
       - name: "run gosec"
         run: |
diff --git a/controllers/argorollouts_controller.go b/controllers/argorollouts_controller.go
index 3021d24..48e4fc8 100644
--- a/controllers/argorollouts_controller.go
+++ b/controllers/argorollouts_controller.go
@@ -37,8 +37,7 @@ var _ reconcile.Reconciler = &RolloutManagerReconciler{}
 // RolloutManagerReconciler reconciles a RolloutManager object
 type RolloutManagerReconciler struct {
 	client.Client
-	Scheme                       *runtime.Scheme
-	OpenShiftRoutePluginLocation string
+	Scheme *runtime.Scheme
 }
 
 var log = logr.Log.WithName("rollouts-controller")
diff --git a/controllers/configmap.go b/controllers/configmap.go
index 70e602b..50c40a8 100644
--- a/controllers/configmap.go
+++ b/controllers/configmap.go
@@ -12,16 +12,11 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-// From https://argo-rollouts.readthedocs.io/en/stable/features/traffic-management/plugins/
-const TrafficRouterPluginConfigMapKey = "trafficRouterPlugins"
-
+// TODO: #22 - Remove this once ConfigMap reconciliation is fixed:
+// nolint:unused
 // Reconcile the Rollouts Default Config Map.
 func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr *rolloutsmanagerv1alpha1.RolloutManager) error {
 
-	if r.OpenShiftRoutePluginLocation == "" { // sanity test the plugin value
-		return fmt.Errorf("OpenShift Route Plugin location is not set")
-	}
-
 	desiredConfigMap := &corev1.ConfigMap{
 		ObjectMeta: metav1.ObjectMeta{
 			Name:      DefaultRolloutsConfigMapName,
@@ -34,7 +29,7 @@ func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr *r
 	trafficRouterPlugins := []types.PluginItem{
 		{
 			Name:     OpenShiftRolloutPluginName,
-			Location: r.OpenShiftRoutePluginLocation,
+			Location: "file://" + OpenShiftRolloutPluginPath,
 		},
 	}
 	pluginString, err := yaml.Marshal(trafficRouterPlugins)
@@ -42,7 +37,7 @@ func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr *r
 		return fmt.Errorf("error marshalling trafficRouterPlugin to string %s", err)
 	}
 	desiredConfigMap.Data = map[string]string{
-		TrafficRouterPluginConfigMapKey: string(pluginString),
+		"trafficRouterPlugins": string(pluginString),
 	}
 
 	actualConfigMap := &corev1.ConfigMap{}
@@ -57,7 +52,7 @@ func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr *r
 	}
 
 	var actualTrafficRouterPlugins []types.PluginItem
-	if err = yaml.Unmarshal([]byte(actualConfigMap.Data[TrafficRouterPluginConfigMapKey]), &actualTrafficRouterPlugins); err != nil {
+	if err = yaml.Unmarshal([]byte(actualConfigMap.Data["trafficRouterPlugins"]), &actualTrafficRouterPlugins); err != nil {
 		return fmt.Errorf("failed to unmarshal traffic router plugins from ConfigMap: %s", err)
 	}
 
@@ -75,7 +70,7 @@ func (r *RolloutManagerReconciler) reconcileConfigMap(ctx context.Context, cr *r
 		return fmt.Errorf("error marshalling trafficRouterPlugin to string %s", err)
 	}
 
-	actualConfigMap.Data[TrafficRouterPluginConfigMapKey] = string(pluginString)
+	actualConfigMap.Data["trafficRouterPlugins"] = string(pluginString)
 
 	return r.Client.Update(ctx, actualConfigMap)
 }
diff --git a/controllers/default.go b/controllers/default.go
index 4038980..ad6a597 100644
--- a/controllers/default.go
+++ b/controllers/default.go
@@ -19,8 +19,9 @@ const (
 	DefaultRolloutsSelectorKey = "app.kubernetes.io/name"
 
 	// OpenShiftRolloutPluginName is the plugin name for Openshift Route Plugin
-	OpenShiftRolloutPluginName = "argoproj-labs/openshift-route-plugin"
-
+	OpenShiftRolloutPluginName = "openshift-route-plugin"
+	// OpenShiftRolloutPluginPath is the path on the rollout controller pod where the plugin will be mounted
+	OpenShiftRolloutPluginPath = "/plugin/rollouts-plugin-trafficrouter-openshift"
 	// DefaultRolloutsConfigMapName is the default name of the ConfigMap that contains the Rollouts controller configuration
 	DefaultRolloutsConfigMapName = "argo-rollouts-config"
 )
diff --git a/controllers/deployment.go b/controllers/deployment.go
index 5987f75..e1f5463 100644
--- a/controllers/deployment.go
+++ b/controllers/deployment.go
@@ -24,7 +24,6 @@ func (r *RolloutManagerReconciler) reconcileRolloutsDeployment(ctx context.Conte
 			Namespace: cr.Namespace,
 		},
 	}
-
 	setRolloutsLabels(&desiredDeployment.ObjectMeta)
 
 	desiredDeployment.Spec = appsv1.DeploymentSpec{
@@ -66,15 +65,8 @@ func (r *RolloutManagerReconciler) reconcileRolloutsDeployment(ctx context.Conte
 		rolloutsContainer(cr),
 	}
 
-	desiredPodSpec.Volumes = []corev1.Volume{
-		{
-			Name: "tmp",
-		},
-	}
-
 	// If the deployment for rollouts does not exist, create one.
 	actualDeployment := &appsv1.Deployment{}
-
 	if err := fetchObject(ctx, r.Client, cr.Namespace, DefaultArgoRolloutsResourceName, actualDeployment); err != nil {
 		if !errors.IsNotFound(err) {
 			return fmt.Errorf("failed to get the deployment %s : %s", DefaultArgoRolloutsResourceName, err)
@@ -90,7 +82,7 @@ func (r *RolloutManagerReconciler) reconcileRolloutsDeployment(ctx context.Conte
 	actualPodSpec := actualDeployment.Spec.Template.Spec
 
 	// If the Deployment already exists, make sure the values we care about are correct.
-	deploymentsDifferent := !reflect.DeepEqual(actualPodSpec.Containers, desiredPodSpec.Containers) ||
+	deploymentsDifferent := !reflect.DeepEqual(actualPodSpec.Containers[0], desiredPodSpec.Containers) ||
 		actualPodSpec.ServiceAccountName != desiredPodSpec.ServiceAccountName ||
 		!reflect.DeepEqual(actualDeployment.Labels, desiredDeployment.Labels) ||
 		!reflect.DeepEqual(actualDeployment.Spec.Template.Labels, desiredDeployment.Spec.Template.Labels) ||
@@ -175,12 +167,6 @@ func rolloutsContainer(cr *rolloutsmanagerv1alpha1.RolloutManager) corev1.Contai
 			ReadOnlyRootFilesystem:   boolPtr(false),
 			RunAsNonRoot:             boolPtr(true),
 		},
-		VolumeMounts: []corev1.VolumeMount{
-			{
-				MountPath: "/home/argo-rollouts/plugin-bin",
-				Name:      "tmp",
-			},
-		},
 	}
 
 }
diff --git a/controllers/reconcile.go b/controllers/reconcile.go
index 6584504..fb87785 100644
--- a/controllers/reconcile.go
+++ b/controllers/reconcile.go
@@ -45,11 +45,13 @@ func (r *RolloutManagerReconciler) reconcileRolloutsManager(ctx context.Context,
 		return err
 	}
 
-	// reconcile configMap for plugins
-	log.Info("reconciling configMap for plugins")
-	if err := r.reconcileConfigMap(ctx, cr); err != nil {
-		return err
-	}
+	// TODO: #22 - Re-enable this once ConfigMap reconciliation is fixed:
+
+	// // reconcile configMap for plugins
+	// log.Info("reconciling configMap for plugins")
+	// if err := r.reconcileConfigMap(ctx, cr); err != nil {
+	// 	return err
+	// }
 
 	log.Info("reconciling rollouts deployment")
 	if err := r.reconcileRolloutsDeployment(ctx, cr, sa); err != nil {
diff --git a/go.mod b/go.mod
index 7b9cc9a..d90287b 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/argoproj-labs/argo-rollouts-manager
 
-go 1.19
+go 1.21
 
 require (
 	github.com/argoproj/argo-rollouts v1.6.6
@@ -53,11 +53,11 @@ require (
 	github.com/spf13/pflag v1.0.5 // indirect
 	go.uber.org/atomic v1.7.0 // indirect
 	go.uber.org/multierr v1.6.0 // indirect
-	golang.org/x/net v0.12.0 // indirect
+	golang.org/x/net v0.17.0 // indirect
 	golang.org/x/oauth2 v0.10.0 // indirect
-	golang.org/x/sys v0.10.0 // indirect
-	golang.org/x/term v0.10.0 // indirect
-	golang.org/x/text v0.11.0 // indirect
+	golang.org/x/sys v0.13.0 // indirect
+	golang.org/x/term v0.13.0 // indirect
+	golang.org/x/text v0.13.0 // indirect
 	golang.org/x/time v0.3.0 // indirect
 	golang.org/x/tools v0.9.3 // indirect
 	gomodules.xyz/jsonpatch/v2 v2.2.0 // indirect
diff --git a/go.sum b/go.sum
index ac1912b..293537d 100644
--- a/go.sum
+++ b/go.sum
@@ -114,6 +114,7 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o
 github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
 github.com/kr/pretty v0.2.0/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI=
 github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
+github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
 github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
 github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
@@ -165,6 +166,7 @@ github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5
 github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
 github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
+github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
 github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU=
 github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415/go.mod h1:GwrjFmJcFw6At/Gs6z4yjiIwzuJ1/+UwLxMQDVQXShQ=
 github.com/xeipuuv/gojsonschema v1.2.0/go.mod h1:anYRn/JVcOK2ZgGU+IjEV4nwlhoK5sQluxsYJ78Id3Y=
@@ -175,6 +177,7 @@ go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw=
 go.uber.org/atomic v1.7.0/go.mod h1:fEN4uk6kAWBTFdckzkM89CLk9XfWZrxpCo0nPH17wJc=
 go.uber.org/goleak v1.1.10/go.mod h1:8a7PlsEVH3e/a/GLqe5IIrQx6GzcnRmZEufDUTk4A7A=
 go.uber.org/goleak v1.2.0 h1:xqgm/S+aQvhWFTtR0XK3Jvg7z8kGV8P4X14IzwN3Eqk=
+go.uber.org/goleak v1.2.0/go.mod h1:XJYK+MuIchqpmGmUSAzotztawfKvYLUIgg7guXrwVUo=
 go.uber.org/multierr v1.6.0 h1:y6IPFStTAIT5Ytl7/XYmHvzXQ7S3g/IeZW9hyZ5thw4=
 go.uber.org/multierr v1.6.0/go.mod h1:cdWPpRnG4AhwMwsgIHip0KRBQjJy5kYEpYjJxpXp9iU=
 go.uber.org/zap v1.19.0/go.mod h1:xg/QME4nWcxGxrpdeYfq7UvYrLh66cuVKdrbD1XF/NI=
@@ -191,6 +194,7 @@ golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHl
 golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA=
 golang.org/x/mod v0.10.0 h1:lFO9qtOdlre5W1jxS3r/4szv2/6iXxScdzjoBMXNhYk=
+golang.org/x/mod v0.10.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
 golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
 golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4=
@@ -204,8 +208,8 @@ golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81R
 golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU=
 golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM=
 golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
-golang.org/x/net v0.12.0 h1:cfawfvKITfUsFCeJIHJrbSxpeu/E81khclypR0GVT50=
-golang.org/x/net v0.12.0/go.mod h1:zEVYFnQC7m/vmpQFELhcD1EWkZlX69l4oqgmer6hfKA=
+golang.org/x/net v0.17.0 h1:pVaXccu2ozPjCXewfr1S7xza/zcXTity9cCdXQYSjIM=
+golang.org/x/net v0.17.0/go.mod h1:NxSsAGuq816PNPmqtQdLE42eU2Fs7NoRIZrHJAlaCOE=
 golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U=
 golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw=
 golang.org/x/oauth2 v0.10.0 h1:zHCpF2Khkwy4mMB4bv0U37YtJdTGW8jI0glAApi0Kh8=
@@ -227,18 +231,18 @@ golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7w
 golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
-golang.org/x/sys v0.10.0 h1:SqMFp9UcQJZa+pmYuAKjd9xq1f0j5rLcDIk0mj4qAsA=
-golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
+golang.org/x/sys v0.13.0 h1:Af8nKPmuFypiUBjVoU9V20FiaFXOcuZI21p0ycVYYGE=
+golang.org/x/sys v0.13.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
 golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
-golang.org/x/term v0.10.0 h1:3R7pNqamzBraeqj/Tj8qt1aQ2HpmlC+Cx/qL/7hn4/c=
-golang.org/x/term v0.10.0/go.mod h1:lpqdcUyK/oCiQxvxVrppt5ggO2KCZ5QblwqPnfZ6d5o=
+golang.org/x/term v0.13.0 h1:bb+I9cTfFazGW51MZqBVmZy7+JEJMouUHTUSKVQLBek=
+golang.org/x/term v0.13.0/go.mod h1:LTmsnFJwVN6bCy1rVCoS+qHT1HhALEFxKncY3WNNh4U=
 golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
 golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.5/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
-golang.org/x/text v0.11.0 h1:LAntKIrcmeSKERyiOh0XMV39LXS8IE9UL2yP7+f5ij4=
-golang.org/x/text v0.11.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
+golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
+golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
 golang.org/x/time v0.3.0 h1:rg5rLMjNzMS1RkNLzCG38eapWhnYLFYXDXj2gOlr8j4=
 golang.org/x/time v0.3.0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
 golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
@@ -293,6 +297,7 @@ gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8
 gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
+gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
 gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc=
 gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw=
 gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
diff --git a/main.go b/main.go
index c6adec6..253305e 100644
--- a/main.go
+++ b/main.go
@@ -49,10 +49,6 @@ func init() {
 	//+kubebuilder:scaffold:scheme
 }
 
-const (
-	DefaultOpenShiftRoutePluginURL = "https://github.com/argoproj-labs/rollouts-plugin-trafficrouter-openshift/releases/download/commit-2749e0ac96ba00ce6f4af19dc6d5358048227d77/rollouts-plugin-trafficrouter-openshift-linux-amd64"
-)
-
 func main() {
 	var metricsAddr string
 	var enableLeaderElection bool
@@ -94,16 +90,9 @@ func main() {
 		os.Exit(1)
 	}
 
-	openShiftRoutePluginLocation := os.Getenv("OPENSHIFT_ROUTE_PLUGIN_LOCATION")
-
-	if openShiftRoutePluginLocation == "" {
-		openShiftRoutePluginLocation = DefaultOpenShiftRoutePluginURL
-	}
-
 	if err = (&controllers.RolloutManagerReconciler{
-		Client:                       mgr.GetClient(),
-		Scheme:                       mgr.GetScheme(),
-		OpenShiftRoutePluginLocation: openShiftRoutePluginLocation,
+		Client: mgr.GetClient(),
+		Scheme: mgr.GetScheme(),
 	}).SetupWithManager(mgr); err != nil {
 		setupLog.Error(err, "unable to create controller", "controller", "RolloutManager")
 		os.Exit(1)


Test patch:
diff --git a/.github/workflows/e2e_tests.yml b/.github/workflows/e2e_tests.yml
index 545ba65..c9396c8 100644
--- a/.github/workflows/e2e_tests.yml
+++ b/.github/workflows/e2e_tests.yml
@@ -31,9 +31,9 @@ jobs:
       - name: Checkout code
         uses: actions/checkout@v2
       - name: Setup Golang
-        uses: actions/setup-go@v1
+        uses: actions/setup-go@v5.0.0
         with:
-          go-version: 1.19
+          go-version-file: './go.mod'          
       - name: GH actions workaround - Kill XSP4 process
         run: |
           sudo pkill mono || true
diff --git a/.github/workflows/rollouts_e2e_tests.yml b/.github/workflows/rollouts_e2e_tests.yml
index 1d6afde..da552a6 100644
--- a/.github/workflows/rollouts_e2e_tests.yml
+++ b/.github/workflows/rollouts_e2e_tests.yml
@@ -31,9 +31,9 @@ jobs:
       - name: Checkout code
         uses: actions/checkout@v2
       - name: Setup Golang
-        uses: actions/setup-go@v1
+        uses: actions/setup-go@v5.0.0
         with:
-          go-version: 1.21
+          go-version-file: './go.mod'          
       - name: GH actions workaround - Kill XSP4 process
         run: |
           sudo pkill mono || true
diff --git a/controllers/configmap_test.go b/controllers/configmap_test.go
deleted file mode 100644
index 2792330..0000000
--- a/controllers/configmap_test.go
+++ /dev/null
@@ -1,119 +0,0 @@
-package rollouts
-
-import (
-	"context"
-
-	"github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
-	"github.com/argoproj/argo-rollouts/utils/plugin/types"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	"gopkg.in/yaml.v2"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-var _ = Describe("ConfigMap Test", func() {
-	var ctx context.Context
-	var a *v1alpha1.RolloutManager
-	var r *RolloutManagerReconciler
-
-	BeforeEach(func() {
-		ctx = context.Background()
-		a = makeTestRolloutManager()
-
-		r = makeTestReconciler(a)
-		Expect(createNamespace(r, a.Namespace)).To(Succeed())
-	})
-
-	It("verifies that the default ConfigMap is created if it is not present", func() {
-		expectedConfigMap := &corev1.ConfigMap{
-			ObjectMeta: metav1.ObjectMeta{
-				Name: DefaultRolloutsConfigMapName,
-			},
-		}
-
-		By("Call reconcileConfigMap")
-		Expect(r.reconcileConfigMap(ctx, a)).To(Succeed())
-
-		By("Verify that the fetched ConfigMap matches the desired one")
-
-		fetchedConfigMap := &corev1.ConfigMap{}
-		Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed())
-
-		Expect(fetchedConfigMap.Name).To(Equal(expectedConfigMap.Name))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation))
-
-		By("Call reconcileConfigMap again")
-		Expect(r.reconcileConfigMap(ctx, a)).To(Succeed())
-
-		By("verifying that the data is still present")
-		Expect(fetchedConfigMap.Name).To(Equal(expectedConfigMap.Name))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation))
-
-	})
-
-	It("verifies that the config map reconciler will not overwrite a custom plugin that is added to the ConfigMap by the user", func() {
-
-		// By("creating a ConfigMap containing default Openshift")
-		expectedConfigMap := &corev1.ConfigMap{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      DefaultRolloutsConfigMapName,
-				Namespace: a.Namespace,
-				Labels: map[string]string{
-					"app.kubernetes.io/name": DefaultRolloutsConfigMapName,
-				},
-			},
-		}
-
-		By("calling reconcileConfigMap, which will add the default plugin to the ConfigMap")
-		Expect(r.reconcileConfigMap(ctx, a)).To(Succeed())
-
-		By("fetching the ConfigMap")
-		fetchedConfigMap := &corev1.ConfigMap{}
-		Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed())
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).ToNot(ContainSubstring("test/plugin"))
-
-		By("adding a new trafficRouter plugin to test the update plugin logic")
-		trafficRouterPlugins := []types.PluginItem{
-			{
-				Name:     "test/plugin",
-				Location: "https://test-path",
-			},
-		}
-
-		newConfigMap := fetchedConfigMap.DeepCopy()
-		{
-			pluginString, err := yaml.Marshal(trafficRouterPlugins)
-			Expect(err).ToNot(HaveOccurred())
-
-			newConfigMap.Data = map[string]string{
-				TrafficRouterPluginConfigMapKey: string(pluginString),
-			}
-		}
-
-		By("updating the ConfigMap to contain only a user provided plugin")
-		Expect(r.Client.Update(ctx, newConfigMap)).To(Succeed())
-
-		By("calling reconcileConfigMap")
-		Expect(r.reconcileConfigMap(ctx, a)).To(Succeed())
-
-		By("verifing that when ConfigMap is reconciled, it contains both plugins")
-
-		Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed())
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test/plugin"))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation))
-
-		By("calling reconcileConfigMap again, to verify nothing changes when reconcile is called again")
-		Expect(r.reconcileConfigMap(ctx, a)).To(Succeed())
-
-		Expect(fetchObject(ctx, r.Client, a.Namespace, expectedConfigMap.Name, fetchedConfigMap)).To(Succeed())
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring("test/plugin"))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(OpenShiftRolloutPluginName))
-		Expect(fetchedConfigMap.Data[TrafficRouterPluginConfigMapKey]).To(ContainSubstring(r.OpenShiftRoutePluginLocation))
-
-	})
-})
diff --git a/controllers/deployment_test.go b/controllers/deployment_test.go
deleted file mode 100644
index 69f8c30..0000000
--- a/controllers/deployment_test.go
+++ /dev/null
@@ -1,133 +0,0 @@
-package rollouts
-
-import (
-	"context"
-
-	"github.com/argoproj-labs/argo-rollouts-manager/api/v1alpha1"
-	. "github.com/onsi/ginkgo/v2"
-	. "github.com/onsi/gomega"
-	appsv1 "k8s.io/api/apps/v1"
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-)
-
-var _ = Describe("Deployment Test", func() {
-	var ctx context.Context
-	var a *v1alpha1.RolloutManager
-	var r *RolloutManagerReconciler
-	var sa *corev1.ServiceAccount
-
-	BeforeEach(func() {
-		ctx = context.Background()
-		a = makeTestRolloutManager()
-
-		r = makeTestReconciler(a)
-		Expect(createNamespace(r, a.Namespace)).To(Succeed())
-
-		sa = &corev1.ServiceAccount{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      DefaultArgoRolloutsResourceName,
-				Namespace: a.Namespace,
-			},
-		}
-		Expect(r.Client.Create(ctx, sa)).To(Succeed())
-	})
-
-	It("should create a new deployment if it does not exist", func() {
-
-		By("calling reconcileRolloutsDeployment")
-		Expect(r.reconcileRolloutsDeployment(ctx, a, sa)).To(Succeed())
-
-		By("fetch the Deployment")
-		fetchedDeployment := &appsv1.Deployment{}
-		Expect(fetchObject(ctx, r.Client, a.Namespace, DefaultArgoRolloutsResourceName, fetchedDeployment)).To(Succeed())
-
-		expectedDeployment := deploymentCR(DefaultArgoRolloutsResourceName, a.Namespace, DefaultArgoRolloutsResourceName, "tmp", "linux", DefaultArgoRolloutsResourceName, a)
-
-		By("verify that the fetched Deployment matches the desired one")
-		Expect(fetchedDeployment.Name).To(Equal(expectedDeployment.Name))
-		Expect(fetchedDeployment.Labels).To(Equal(expectedDeployment.Labels))
-		Expect(fetchedDeployment.Spec.Template.Spec.ServiceAccountName).To(Equal(expectedDeployment.Spec.Template.Spec.ServiceAccountName))
-		Expect(fetchedDeployment.Spec.Template.Labels).To(Equal(expectedDeployment.Spec.Template.Labels))
-		Expect(fetchedDeployment.Spec.Selector).To(Equal(expectedDeployment.Spec.Selector))
-		Expect(fetchedDeployment.Spec.Template.Spec.NodeSelector).To(Equal(expectedDeployment.Spec.Template.Spec.NodeSelector))
-		Expect(fetchedDeployment.Spec.Template.Spec.Tolerations).To(Equal(expectedDeployment.Spec.Template.Spec.Tolerations))
-		Expect(fetchedDeployment.Spec.Template.Spec.SecurityContext).To(Equal(expectedDeployment.Spec.Template.Spec.SecurityContext))
-		Expect(fetchedDeployment.Spec.Template.Spec.Volumes).To(Equal(expectedDeployment.Spec.Template.Spec.Volumes))
-	})
-
-	It("should update the Deployment back to default values, if deployment already exists and has been modified away from default", func() {
-
-		By("create a new Deployment with custom values")
-		existingDeployment := deploymentCR(DefaultArgoRolloutsResourceName, a.Namespace, "test-resource-name", "tmp-test", "linux-test", sa.Name, a)
-
-		Expect(r.Client.Create(ctx, existingDeployment)).To(Succeed())
-
-		By("calling reconcileRolloutsDeployment")
-		Expect(r.reconcileRolloutsDeployment(ctx, a, sa)).To(Succeed())
-
-		By("fetch the Deployment")
-		fetchedDeployment := &appsv1.Deployment{}
-		Expect(fetchObject(ctx, r.Client, a.Namespace, DefaultArgoRolloutsResourceName, fetchedDeployment)).To(Succeed())
-
-		expectedDeployment := deploymentCR(DefaultArgoRolloutsResourceName, a.Namespace, DefaultArgoRolloutsResourceName, "tmp", "linux", sa.Name, a)
-
-		By("verify that the Deployment has been reconciled back to default values")
-		Expect(fetchedDeployment.Name).To(Equal(expectedDeployment.Name))
-		Expect(fetchedDeployment.Labels).To(Equal(expectedDeployment.Labels))
-		Expect(fetchedDeployment.Spec.Template.Spec.ServiceAccountName).To(Equal(expectedDeployment.Spec.Template.Spec.ServiceAccountName))
-		Expect(fetchedDeployment.Spec.Template.Labels).To(Equal(expectedDeployment.Spec.Template.Labels))
-		Expect(fetchedDeployment.Spec.Selector).To(Equal(expectedDeployment.Spec.Selector))
-		Expect(fetchedDeployment.Spec.Template.Spec.NodeSelector).To(Equal(expectedDeployment.Spec.Template.Spec.NodeSelector))
-		Expect(fetchedDeployment.Spec.Template.Spec.Tolerations).To(Equal(expectedDeployment.Spec.Template.Spec.Tolerations))
-		Expect(fetchedDeployment.Spec.Template.Spec.SecurityContext).To(Equal(expectedDeployment.Spec.Template.Spec.SecurityContext))
-		Expect(fetchedDeployment.Spec.Template.Spec.Volumes).To(Equal(expectedDeployment.Spec.Template.Spec.Volumes))
-
-	})
-
-})
-
-func deploymentCR(name string, namespace string, label string, volumeName string, nodeSelector string, serviceAccount string, rolloutManager *v1alpha1.RolloutManager) *appsv1.Deployment {
-	runAsNonRoot := true
-	deploymentCR := &appsv1.Deployment{
-		ObjectMeta: metav1.ObjectMeta{
-			Name:      name,
-			Namespace: namespace,
-		},
-	}
-	setRolloutsLabels(&deploymentCR.ObjectMeta)
-	deploymentCR.Spec = appsv1.DeploymentSpec{
-		Selector: &metav1.LabelSelector{
-			MatchLabels: map[string]string{
-				DefaultRolloutsSelectorKey: label,
-			},
-		},
-		Template: corev1.PodTemplateSpec{
-			ObjectMeta: metav1.ObjectMeta{
-				Labels: map[string]string{
-					DefaultRolloutsSelectorKey: label,
-				},
-			},
-			Spec: corev1.PodSpec{
-				Volumes: []corev1.Volume{
-					{
-						Name: volumeName,
-					},
-				},
-				NodeSelector: map[string]string{
-					"kubernetes.io/os": nodeSelector,
-				},
-				Containers: []corev1.Container{
-					rolloutsContainer(rolloutManager),
-				},
-				ServiceAccountName: serviceAccount,
-				SecurityContext: &corev1.PodSecurityContext{
-					RunAsNonRoot: &runAsNonRoot,
-				},
-			},
-		},
-	}
-
-	return deploymentCR
-
-}
diff --git a/controllers/resources_test.go b/controllers/resources_test.go
index def5f30..f326527 100644
--- a/controllers/resources_test.go
+++ b/controllers/resources_test.go
@@ -26,7 +26,8 @@ var _ = Describe("ReconcileRolloutManager tests", func() {
 		ctx = context.Background()
 		a = makeTestRolloutManager()
 		r = makeTestReconciler(a)
-		Expect(createNamespace(r, a.Namespace)).To(Succeed())
+		err := createNamespace(r, a.Namespace)
+		Expect(err).ToNot(HaveOccurred())
 
 		req = reconcile.Request{
 			NamespacedName: types.NamespacedName{
@@ -88,20 +89,43 @@ var _ = Describe("ReconcileRolloutManager tests", func() {
 	})
 
 	It("ReconcileAggregate to adminClusterRole test", func() {
-		Expect(r.reconcileRolloutsAggregateToAdminClusterRole(context.Background(), a)).To(Succeed())
+		err := r.reconcileRolloutsAggregateToAdminClusterRole(context.Background(), a)
+		Expect(err).ToNot(HaveOccurred())
 	})
 
 	It("ReconcileAggregate to EditClusterRole test", func() {
-		Expect(r.reconcileRolloutsAggregateToEditClusterRole(context.Background(), a)).To(Succeed())
+		err := r.reconcileRolloutsAggregateToEditClusterRole(context.Background(), a)
+		Expect(err).ToNot(HaveOccurred())
 	})
 
 	It("ReconcileAggregate to ViewClusterRole", func() {
-		Expect(r.reconcileRolloutsAggregateToViewClusterRole(context.Background(), a)).To(Succeed())
+		err := r.reconcileRolloutsAggregateToViewClusterRole(context.Background(), a)
+		Expect(err).ToNot(HaveOccurred())
 	})
 
 	Context("RolloutManager Cleaup tests", func() {
+		ctx = context.Background()
 		a = makeTestRolloutManager()
 
+		req = reconcile.Request{
+			NamespacedName: types.NamespacedName{
+				Name:      a.Name,
+				Namespace: a.Namespace,
+			},
+		}
+		resources := []runtime.Object{a}
+
+		r := makeTestReconciler(resources...)
+		err := createNamespace(r, a.Namespace)
+		Expect(err).ToNot(HaveOccurred())
+
+		res, err := r.Reconcile(ctx, req)
+		Expect(err).NotTo(HaveOccurred())
+		Expect(res.Requeue).Should(BeFalse(), "reconcile should not requeue request")
+
+		err = r.Client.Delete(ctx, a)
+		Expect(err).ToNot(HaveOccurred())
+
 		tt := []struct {
 			name     string
 			resource client.Object
@@ -156,28 +180,9 @@ var _ = Describe("ReconcileRolloutManager tests", func() {
 
 		for _, test := range tt {
 			When(test.name, func() {
-				It("Cleans up all resources created for RolloutManager", func() {
-
-					ctx := context.Background()
-					req := reconcile.Request{
-						NamespacedName: types.NamespacedName{
-							Name:      a.Name,
-							Namespace: a.Namespace,
-						},
-					}
-					resources := []runtime.Object{a}
-
-					r := makeTestReconciler(resources...)
-					err := createNamespace(r, a.Namespace)
-					Expect(err).ToNot(HaveOccurred())
-
-					res, err := r.Reconcile(ctx, req)
-					Expect(err).NotTo(HaveOccurred())
-					Expect(res.Requeue).Should(BeFalse(), "reconcile should not requeue request")
-
-					err = r.Client.Delete(ctx, a)
-					Expect(err).ToNot(HaveOccurred())
-					Expect(fetchObject(ctx, r.Client, a.Namespace, test.name, test.resource)).ToNot(Succeed(), fmt.Sprintf("Expected %s to be deleted", test.name))
+				It("ReconcileRolloutManager CleanUp Test for "+test.name+".", func() {
+					err := fetchObject(ctx, r.Client, a.Namespace, test.name, test.resource)
+					Expect(err).To(HaveOccurred(), fmt.Sprintf("Expected %s to be deleted", test.name))
 				})
 			})
 		}
diff --git a/controllers/status_test.go b/controllers/status_test.go
index e600933..ca075f7 100644
--- a/controllers/status_test.go
+++ b/controllers/status_test.go
@@ -16,8 +16,11 @@ var _ = Describe("RolloutManager Test", func() {
 		a := makeTestRolloutManager()
 
 		r := makeTestReconciler(a)
-		Expect(createNamespace(r, a.Namespace)).To(Succeed())
-		Expect(r.reconcileStatus(ctx, a)).To(Succeed())
+		err := createNamespace(r, a.Namespace)
+		Expect(err).ToNot(HaveOccurred())
+
+		err = r.reconcileStatus(ctx, a)
+		Expect(err).ToNot(HaveOccurred())
 
 		By("When deployment for rollout controller does not exist")
 		Expect(a.Status.RolloutController).To(Equal(rolloutsmanagerv1alpha1.PhaseFailure))
@@ -31,8 +34,11 @@ var _ = Describe("RolloutManager Test", func() {
 			},
 		}
 
-		Expect(r.Client.Create(ctx, deploy)).To(Succeed())
-		Expect(r.reconcileStatus(ctx, a)).To(Succeed())
+		err = r.Client.Create(ctx, deploy)
+		Expect(err).ToNot(HaveOccurred())
+
+		err = r.reconcileStatus(ctx, a)
+		Expect(err).ToNot(HaveOccurred())
 
 		Expect(a.Status.RolloutController).To(Equal(rolloutsmanagerv1alpha1.PhaseUnknown))
 		Expect(a.Status.Phase).To(Equal(rolloutsmanagerv1alpha1.PhaseUnknown))
@@ -42,19 +48,24 @@ var _ = Describe("RolloutManager Test", func() {
 		deploy.Status.ReadyReplicas = 0
 		deploy.Spec.Replicas = &requiredReplicas
 
-		Expect(r.Client.Update(ctx, deploy)).To(Succeed())
+		err = r.Client.Update(ctx, deploy)
+		Expect(err).ToNot(HaveOccurred())
 
-		Expect(r.reconcileStatus(ctx, a)).To(Succeed())
+		err = r.reconcileStatus(ctx, a)
+		Expect(err).ToNot(HaveOccurred())
 
 		Expect(a.Status.RolloutController).To(Equal(rolloutsmanagerv1alpha1.PhasePending))
 		Expect(a.Status.Phase).To(Equal(rolloutsmanagerv1alpha1.PhasePending))
 
-		By("When deployment exists and required number of replicas are up and running.")
+		// When deployment exists and required number of replicas are up and running.
 		deploy.Status.ReadyReplicas = 1
 		deploy.Spec.Replicas = &requiredReplicas
 
-		Expect(r.Client.Update(ctx, deploy)).To(Succeed())
-		Expect(r.reconcileStatus(ctx, a)).To(Succeed())
+		err = r.Client.Update(ctx, deploy)
+		Expect(err).ToNot(HaveOccurred())
+
+		err = r.reconcileStatus(ctx, a)
+		Expect(err).ToNot(HaveOccurred())
 
 		Expect(a.Status.RolloutController).To(Equal(rolloutsmanagerv1alpha1.PhaseAvailable))
 		Expect(a.Status.Phase).To(Equal(rolloutsmanagerv1alpha1.PhaseAvailable))
diff --git a/controllers/utils_test.go b/controllers/utils_test.go
index 89c574b..9139eca 100644
--- a/controllers/utils_test.go
+++ b/controllers/utils_test.go
@@ -40,9 +40,8 @@ func makeTestReconciler(objs ...runtime.Object) *RolloutManagerReconciler {
 
 	cl := fake.NewClientBuilder().WithScheme(s).WithRuntimeObjects(objs...).Build()
 	return &RolloutManagerReconciler{
-		Client:                       cl,
-		Scheme:                       s,
-		OpenShiftRoutePluginLocation: "file://non-empty-test-url", // Set a non-real, non-empty value for unit tests: override this to test a specific value
+		Client: cl,
+		Scheme: s,
 	}
 }
 
diff --git a/hack/run-upstream-argo-rollouts-e2e-tests.sh b/hack/run-upstream-argo-rollouts-e2e-tests.sh
index ff7ab91..be288c8 100755
--- a/hack/run-upstream-argo-rollouts-e2e-tests.sh
+++ b/hack/run-upstream-argo-rollouts-e2e-tests.sh
@@ -92,43 +92,7 @@ make test-e2e | tee /tmp/test-e2e.log
 
 set +x
 
-# 6) Check the results for unexpected failures
-
-# Here we grep out the failures we expect, as of January 2024:
-# - Most of these tests fail 100% of the time, because they were not designed to run against Argo Rollouts running on a cluster. (These are safe to ignore.)
-#     - The rollouts tests are written to assume they are running locally: not within a container, and not on a K8s cluster.
-# - Some are intermittently failing, implying a race condition in the test/product.
-# - Finally, some still need to be investigated, to determine why they are failing in this case.
-UNEXPECTED_FAILURES=`cat /tmp/test-e2e.log | grep "FAIL:" | grep -v "re-run" \
-  | grep -v "TestAPISIXSuite (" \
-  | grep -v "TestFunctionalSuite (" \
-  | grep -v "TestCanarySuite (" \
-  | grep -v "TestAWSSuite (" \
-  | grep -v "TestExperimentSuite (" \
-  | grep -v "TestControllerMetrics" \
-  | grep -v "TestAPISIXCanarySetHeaderStep" \
-  | grep -v "TestALBExperimentStep " \
-  | grep -v "TestALBExperimentStepNoSetWeightMultiIngress"  \
-  | grep -v "TestCanaryDynamicStableScale"  \
-  | grep -v "TestExperimentWithDryRunMetrics" \
-  | grep -v "TestBlueGreenPromoteFull" \
-  | grep -v "TestALBExperimentStepNoSetWeight" \
-  | grep -v "TestCanaryScaleDownOnAbort"`
-
-# As of January 2024 (Rollouts v1.6.4):
-# 
-# Always fail:
-# - TestCanaryDynamicStableScale
-# - TestCanaryScaleDownOnAbort
-# - TestAPISIXCanarySetHeaderStep
-# - TestControllerMetrics (also fails when running upstream rollouts as a container)
-# - TestExperimentWithDryRunMetrics (also fails when running upstream rollouts as a container)
-#
-# Intermittently fail:
-# - TestBlueGreenPromoteFull
-# - TestALBExperimentStepNoSetWeight
-# - TestALBExperimentStep
-# - TestALBExperimentStepNoSetWeightMultiIngress
+# 6) Check and report the results for unexpected failures
 
 echo "-----------------------------------------------------------------"
 echo
@@ -142,11 +106,9 @@ cat /tmp/test-e2e.log | grep "    --- FAIL:" | grep -v "re-run" | sort -u
 echo
 echo
 
-if [ -n "$UNEXPECTED_FAILURES" ]; then
-  echo "* FAIL: Unexpected failures occurred."
-  exit 1
-else
-  echo "* SUCCESS: No unexpected errors occurred."
-fi
+set -e
 
+# Call a small Go script to verify expected test failures. See Go file for details.
+"$SCRIPT_DIR/verify-rollouts-e2e-tests/verify-e2e-test-results.sh" /tmp/test-e2e.log
 
+echo "* SUCCESS: No unexpected errors occurred."
diff --git a/hack/verify-rollouts-e2e-tests/go.mod b/hack/verify-rollouts-e2e-tests/go.mod
new file mode 100644
index 0000000..e11fc89
--- /dev/null
+++ b/hack/verify-rollouts-e2e-tests/go.mod
@@ -0,0 +1,3 @@
+module github.com/argoproj-labs/argo-rollouts-manager/verify-rollouts-e2e-tests
+
+go 1.20
diff --git a/hack/verify-rollouts-e2e-tests/main.go b/hack/verify-rollouts-e2e-tests/main.go
new file mode 100644
index 0000000..d2d0bac
--- /dev/null
+++ b/hack/verify-rollouts-e2e-tests/main.go
@@ -0,0 +1,238 @@
+package main
+
+import (
+	"fmt"
+	"os"
+	"sort"
+	"strings"
+	"time"
+)
+
+func main() {
+
+	// As of March 2024 (Rollouts v1.6.6):
+	//
+	// Always fail:
+	// - TestAPISIXCanarySetHeaderStep
+	// - TestExperimentWithDryRunMetrics (also fails when running upstream rollouts as a container)
+	// - TestControllerMetrics (also fails when running upstream rollouts as a container)
+	//
+	// Often fail:
+	// - TestBlueGreenPromoteFull
+	//
+	// Intermittently fail:
+	// - TestCanaryDynamicStableScale
+	// - TestCanaryScaleDownOnAbort
+	// - TestALBExperimentStepNoSetWeight
+	// - TestALBExperimentStep
+	// - TestALBExperimentStepNoSetWeightMultiIngress
+
+	// - Most of these tests fail 100% of the time, because they were not designed to run against Argo Rollouts running on a cluster. (These are safe to ignore.)
+	//   - The rollouts tests are written to assume they are running locally: not within a container, and not on a K8s cluster.
+	// - Some are intermittently failing, implying a race condition in the test/product.
+
+	testsExpectedToFailList := []string{
+		"TestAPISIXSuite/TestAPISIXCanarySetHeaderStep",
+		"TestExperimentSuite/TestExperimentWithDryRunMetrics",
+		"TestFunctionalSuite/TestControllerMetrics",
+		"TestFunctionalSuite/TestBlueGreenPromoteFull",
+	}
+
+	// DONE 6 runs, 144 tests, 6 skipped, 47 failures in 2279.668s
+
+	// 1) Read E2E test log, output by run-upstream-argo-rollouts-e2e-tests.sh
+	if len(os.Args) != 2 {
+		reportErrorAndExit(fmt.Errorf("expected args: path to E2E test log"))
+		return
+	}
+
+	testsE2EResultsLogPath := os.Args[1]
+
+	fileContents, err := waitAndGetE2EFileContents(testsE2EResultsLogPath)
+	if err != nil {
+		reportErrorAndExit(err)
+		return
+	}
+	fmt.Println()
+
+	// 2) Parse E2E test log, skipping any tests that we expect to fail
+	// - map: name of test -> list of test run lines
+	testResults, err := parseTestResultsFromFile(fileContents, testsExpectedToFailList)
+	if err != nil {
+		reportErrorAndExit(err)
+		return
+	}
+
+	// 3) Report unexpected failed tests, in alphabetical order
+	mapKeys := []string{}
+	for key := range testResults {
+		mapKeys = append(mapKeys, key)
+	}
+
+	sort.Strings(mapKeys)
+
+	if len(mapKeys) == 0 { // sanity test
+		reportErrorAndExit(fmt.Errorf("no test results found"))
+		return
+	}
+
+	atLeastOneTestPermFail := false
+
+	for _, testName := range mapKeys {
+
+		testRuns := testResults[testName]
+
+		// gotestsum will rerun Argo Rollouts tests up to 5 times (6 runs total)
+		// - Here we check if there exists AT LEAST ONE 'PASS'
+		// - If no 'PASS' results exist, it implies the test never passed after all retries, so it's a permfail
+
+		passFound := false
+		for _, testRunLine := range testRuns {
+			if strings.Contains(testRunLine, "PASS") {
+				passFound = true
+			}
+		}
+
+		if !passFound {
+			fmt.Println("Unexpected test failure, test failed too many times - " + testName + ":")
+			for _, testRun := range testRuns {
+				fmt.Println(testRun)
+			}
+
+			fmt.Println()
+			atLeastOneTestPermFail = true
+
+		}
+
+	}
+
+	// 4) Exit with error code 1 if there was at least one unexpected test failure.
+	if atLeastOneTestPermFail {
+		reportErrorAndExit(fmt.Errorf("at least one test failure occurred"))
+		return
+	}
+
+}
+
+// parseTestResultsFromFile parses the E2E test log, skipping any tests that we expect to fail, and storing the results in a map
+func parseTestResultsFromFile(fileContents []string, testsExpectedToFailList []string) (map[string][]string, error) {
+
+	atLeastOnePassSeen := false
+	atLeastOneFailSeen := false
+
+	testResults := map[string][]string{}
+
+	testsExpectedToFailMap := map[string]any{}
+
+	for _, testExpectedToFail := range testsExpectedToFailList {
+		testsExpectedToFailMap[testExpectedToFail] = ""
+	}
+
+	for _, line := range fileContents {
+
+		testSlashE2EText := "test/e2e"
+
+		if !strings.Contains(line, testSlashE2EText) {
+			continue
+		}
+
+		var testName string
+
+		if strings.HasPrefix(line, "===") && strings.Contains(line, "FAIL") {
+			// example: === FAIL: test/e2e TestFunctionalSuite/TestBlueGreenPromoteFull (unknown)
+			//
+			// Unfortunately, the FAIL line is surrounded by invisible ANSI colour whitespace, so we can't scan for it directly.
+			// - we instead check for ===, FAIL, and the test/e2e string.
+
+			testName = line[strings.Index(line, testSlashE2EText)+len(testSlashE2EText)+1 : strings.Index(line, "(")-1]
+			testName = strings.TrimSpace(testName)
+
+			if !strings.Contains(testName, "/") {
+				// Ignore the fail reported for the suite: we only care about individual test fails
+				continue
+			}
+
+			atLeastOneFailSeen = true
+
+		} else if strings.Contains(line, "PASS") && strings.Contains(line, ".") {
+			// example: PASS test/e2e.TestCanarySuite/TestCanaryDynamicStableScale (20.91s)
+
+			roundBraceIndex := strings.Index(line, "(")
+
+			if roundBraceIndex == -1 {
+				continue
+			}
+
+			testName = line[strings.Index(line, ".")+1 : roundBraceIndex-1]
+			atLeastOnePassSeen = true
+
+		} else {
+			continue
+		}
+
+		if _, exists := testsExpectedToFailMap[testName]; exists {
+			// Ignore tests that are expected to fail
+			continue
+		}
+
+		if !strings.Contains(testName, "/") {
+			// Skip suite-only results
+			continue
+		}
+
+		testResults[testName] = append(testResults[testName], line)
+	}
+
+	if !atLeastOneFailSeen || !atLeastOnePassSeen { // sanity test: BOTH a pass and fail should have occurred in the log.
+		return nil, fmt.Errorf("there may be something wrong with the parser: we expect to see both at least one pass, and at least one fail, in the parsed output: %v %v", atLeastOneFailSeen, atLeastOnePassSeen)
+	}
+
+	return testResults, nil
+}
+
+// waitAndGetE2EFileContents waits for the last line of the file to start with 'DONE' before returning the contents
+// This allows us to avoid a race condition with tee where the file contents of the file may not have been fully flushed.
+func waitAndGetE2EFileContents(testsE2EResultsLogPath string) ([]string, error) {
+
+	for {
+
+		fileLines, err := readFileIntoListOfLines(testsE2EResultsLogPath, true)
+		if err != nil {
+			return []string{}, err
+		}
+
+		lastLine := fileLines[len(fileLines)-1]
+
+		// example: DONE 6 runs, 144 tests, 6 skipped, 47 failures in 2279.668s
+		if strings.HasPrefix(lastLine, "DONE ") {
+			fmt.Println("E2E tests file is complete:", lastLine)
+			return fileLines, nil
+		}
+
+		// Otherwise, wait a second then check again.
+		fmt.Println("* Waiting for E2E test file to be complete:", lastLine)
+		time.Sleep(time.Second)
+	}
+
+}
+
+func reportErrorAndExit(err error) {
+	fmt.Println("ERROR:", err)
+	os.Exit(1)
+}
+
+func readFileIntoListOfLines(path string, initialTrimSpace bool) ([]string, error) {
+	fileContentsBytes, err := os.ReadFile(path)
+	if err != nil {
+		return []string{}, err
+	}
+
+	fileContents := string(fileContentsBytes)
+
+	if initialTrimSpace {
+		fileContents = strings.TrimSpace(fileContents)
+	}
+
+	return strings.Split(string(fileContents), "\n"), nil
+
+}
diff --git a/hack/verify-rollouts-e2e-tests/verify-e2e-test-results.sh b/hack/verify-rollouts-e2e-tests/verify-e2e-test-results.sh
new file mode 100755
index 0000000..a462227
--- /dev/null
+++ b/hack/verify-rollouts-e2e-tests/verify-e2e-test-results.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+
+SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )
+
+cd $SCRIPT_DIR
+
+go run . $*