Repository: Azure/ARO-RP
Pull request #1508 (2021-05-20 11:24:43)

Operator SDK decommissioning  
### Which issue this PR addresses:

Fixes https://msazure.visualstudio.com/AzureRedHatOpenShift/_workitems/edit/9534793

### What this PR does / why we need it:

Each time we need to touch Operator SDK code in our repo little cat in the world dies from stress. 

### Test plan for issue:

Unit tests

### Is there any documentation that needs to be updated for this PR?

No


github-actions[bot]: Please rebase pull request.
mjudeikis: Very good catch :) checked apimachinery but not core! :D 
mjudeikis: /azp run ci
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
mjudeikis: /azp run ci
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
mjudeikis: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
mjudeikis: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
github-actions[bot]: Please rebase pull request.
mjudeikis: /azp run e2e
azure-pipelines[bot]: <samp>
Azure Pipelines successfully started running 1 pipeline(s).<br>

</samp>
mjudeikis: Either lets merge it or drop it. This so much nit'ing I don't think it is worth time stent now. 
m1kola: @mjudeikis some of the comments were nits, I agree. But tests were not covering several cases, but at the same time had duplicates. 

I know you are busy, so I prepared a patch for the remainig feedback.

```patch
diff --git a/pkg/monitor/cluster/arooperatorconditions.go b/pkg/monitor/cluster/arooperatorconditions.go
index f090d4e6..1c0b6269 100644
--- a/pkg/monitor/cluster/arooperatorconditions.go
+++ b/pkg/monitor/cluster/arooperatorconditions.go
@@ -13,7 +13,7 @@ import (
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 )
 
-var aroOperatorConditionsExpected = map[arov1alpha1.CondititionType]operatorv1.ConditionStatus{
+var aroOperatorConditionsExpected = map[string]operatorv1.ConditionStatus{
 	arov1alpha1.InternetReachableFromMaster: operatorv1.ConditionTrue,
 	arov1alpha1.InternetReachableFromWorker: operatorv1.ConditionTrue,
 }
@@ -25,7 +25,7 @@ func (mon *Monitor) emitAroOperatorConditions(ctx context.Context) error {
 	}
 
 	for _, c := range cluster.Status.Conditions {
-		if aroOperatorConditionsExpected[arov1alpha1.CondititionType(c.Type)] == c.Status {
+		if aroOperatorConditionsExpected[c.Type] == c.Status {
 			continue
 		}
 
diff --git a/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go b/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
index d78bdac6..72cd9865 100644
--- a/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
+++ b/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
@@ -16,17 +16,15 @@ const (
 	ServicePrincipalValid       = "ServicePrincipalValid"
 )
 
-type CondititionType string
-
 // AllConditionTypes is a operator conditions currently in use, any condition not in this list is not
 // added to the operator.status.conditions list
-func AllConditionTypes() []CondititionType {
-	return []CondititionType{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
+func AllConditionTypes() []string {
+	return []string{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
 }
 
 // ClusterChecksTypes represents checks performed on the cluster to verify basic functionality
-func ClusterChecksTypes() []CondititionType {
-	return []CondititionType{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
+func ClusterChecksTypes() []string {
+	return []string{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
 }
 
 type GenevaLoggingSpec struct {
diff --git a/pkg/util/conditions/condition.go b/pkg/util/conditions/condition.go
index afedb121..e1e5531d 100644
--- a/pkg/util/conditions/condition.go
+++ b/pkg/util/conditions/condition.go
@@ -47,15 +47,15 @@ func SetCondition(ctx context.Context, arocli aroclient.Interface, cond *operato
 	})
 }
 
-func IsTrue(conditions []operatorv1.OperatorCondition, t arov1alpha1.CondititionType) bool {
+func IsTrue(conditions []operatorv1.OperatorCondition, t string) bool {
 	return isCondition(conditions, t, operatorv1.ConditionTrue)
 }
 
-func IsFalse(conditions []operatorv1.OperatorCondition, t arov1alpha1.CondititionType) bool {
+func IsFalse(conditions []operatorv1.OperatorCondition, t string) bool {
 	return isCondition(conditions, t, operatorv1.ConditionFalse)
 }
 
-func isCondition(conditions []operatorv1.OperatorCondition, t arov1alpha1.CondititionType, s operatorv1.ConditionStatus) bool {
+func isCondition(conditions []operatorv1.OperatorCondition, t string, s operatorv1.ConditionStatus) bool {
 	for _, condition := range conditions {
 		if condition.Type == string(t) && condition.Status == s {
 			return true
@@ -70,13 +70,13 @@ func cleanStaleConditions(cluster *arov1alpha1.Cluster, role string) (changed bo
 	conditions := make([]operatorv1.OperatorCondition, 0, len(cluster.Status.Conditions))
 
 	// cleanup any old conditions
-	current := map[arov1alpha1.CondititionType]bool{}
+	current := map[string]bool{}
 	for _, ct := range arov1alpha1.AllConditionTypes() {
 		current[ct] = true
 	}
 
 	for _, cond := range cluster.Status.Conditions {
-		if _, ok := current[arov1alpha1.CondititionType(cond.Type)]; ok {
+		if _, ok := current[cond.Type]; ok {
 			conditions = append(conditions, cond)
 		} else {
 			changed = true
diff --git a/pkg/util/conditions/condition_test.go b/pkg/util/conditions/condition_test.go
index 4ea46562..bcfb4cee 100644
--- a/pkg/util/conditions/condition_test.go
+++ b/pkg/util/conditions/condition_test.go
@@ -7,6 +7,7 @@ import (
 	"context"
 	"reflect"
 	"testing"
+	"time"
 
 	operatorv1 "github.com/openshift/api/operator/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -32,25 +33,47 @@ func TestSetCondition(t *testing.T) {
 		name      string
 		aroclient aroclient.Interface
 		objects   []runtime.Object
-		input     operatorv1.OperatorCondition
+		input     *operatorv1.OperatorCondition
 
 		expected arov1alpha1.ClusterStatus
 		wantErr  error
 	}{
 		{
-			name: "no condition change",
+			name: "no condition provided",
 			objects: []runtime.Object{&arov1alpha1.Cluster{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: objectName,
 				},
 			}},
+			expected: arov1alpha1.ClusterStatus{},
+		},
+		{
+			name: "new condition provided",
+			objects: []runtime.Object{&arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: objectName,
+				},
+				Status: arov1alpha1.ClusterStatus{
+					Conditions:      []operatorv1.OperatorCondition{},
+					OperatorVersion: version,
+				},
+			}},
+			input: &operatorv1.OperatorCondition{
+				Type:   arov1alpha1.InternetReachableFromMaster,
+				Status: operatorv1.ConditionFalse,
+			},
 			expected: arov1alpha1.ClusterStatus{
+				Conditions: []operatorv1.OperatorCondition{
+					{
+						Type:   arov1alpha1.InternetReachableFromMaster,
+						Status: operatorv1.ConditionFalse,
+					},
+				},
 				OperatorVersion: version,
-				Conditions:      []operatorv1.OperatorCondition{},
 			},
 		},
 		{
-			name: "update operator version",
+			name: "condition provided without status change - only update operator version",
 			objects: []runtime.Object{&arov1alpha1.Cluster{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: objectName,
@@ -65,6 +88,10 @@ func TestSetCondition(t *testing.T) {
 					OperatorVersion: "?",
 				},
 			}},
+			input: &operatorv1.OperatorCondition{
+				Type:   arov1alpha1.InternetReachableFromMaster,
+				Status: operatorv1.ConditionFalse,
+			},
 			expected: arov1alpha1.ClusterStatus{
 				Conditions: []operatorv1.OperatorCondition{
 					{
@@ -76,7 +103,7 @@ func TestSetCondition(t *testing.T) {
 			},
 		},
 		{
-			name: "change with condition",
+			name: "condition provided without status change - no update",
 			objects: []runtime.Object{&arov1alpha1.Cluster{
 				ObjectMeta: metav1.ObjectMeta{
 					Name: objectName,
@@ -84,22 +111,25 @@ func TestSetCondition(t *testing.T) {
 				Status: arov1alpha1.ClusterStatus{
 					Conditions: []operatorv1.OperatorCondition{
 						{
-							Type:   arov1alpha1.InternetReachableFromMaster,
-							Status: operatorv1.ConditionFalse,
+							Type:               arov1alpha1.InternetReachableFromMaster,
+							Status:             operatorv1.ConditionFalse,
+							LastTransitionTime: metav1.Time{Time: time.Date(1970, 0, 0, 0, 0, 0, 0, time.UTC)},
 						},
 					},
+					OperatorVersion: version,
 				},
 			}},
-			input: operatorv1.OperatorCondition{
-				Type:   arov1alpha1.InternetReachableFromMaster,
-				Status: operatorv1.ConditionTrue,
+			input: &operatorv1.OperatorCondition{
+				Type:               arov1alpha1.InternetReachableFromMaster,
+				Status:             operatorv1.ConditionFalse,
+				LastTransitionTime: metav1.Time{Time: time.Date(2021, 0, 0, 0, 0, 0, 0, time.UTC)},
 			},
 			expected: arov1alpha1.ClusterStatus{
 				Conditions: []operatorv1.OperatorCondition{
 					{
 						Type:               arov1alpha1.InternetReachableFromMaster,
-						Status:             operatorv1.ConditionTrue,
-						LastTransitionTime: transitionTime,
+						Status:             operatorv1.ConditionFalse,
+						LastTransitionTime: metav1.Time{Time: time.Date(1970, 0, 0, 0, 0, 0, 0, time.UTC)},
 					},
 				},
 				OperatorVersion: version,
@@ -122,9 +152,10 @@ func TestSetCondition(t *testing.T) {
 							Status: operatorv1.ConditionFalse,
 						},
 					},
+					OperatorVersion: "?",
 				},
 			}},
-			input: operatorv1.OperatorCondition{
+			input: &operatorv1.OperatorCondition{
 				Type:   arov1alpha1.InternetReachableFromMaster,
 				Status: operatorv1.ConditionTrue,
 			},
@@ -143,12 +174,42 @@ func TestSetCondition(t *testing.T) {
 				OperatorVersion: version,
 			},
 		},
+		{
+			name: "cleanup stale conditions",
+			objects: []runtime.Object{&arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: objectName,
+				},
+				Status: arov1alpha1.ClusterStatus{
+					Conditions: []operatorv1.OperatorCondition{
+						{
+							Type:   "staleType",
+							Status: operatorv1.ConditionTrue,
+						},
+					},
+					OperatorVersion: version,
+				},
+			}},
+			input: &operatorv1.OperatorCondition{
+				Type:   arov1alpha1.InternetReachableFromMaster,
+				Status: operatorv1.ConditionTrue,
+			},
+			expected: arov1alpha1.ClusterStatus{
+				Conditions: []operatorv1.OperatorCondition{
+					{
+						Type:               arov1alpha1.InternetReachableFromMaster,
+						Status:             operatorv1.ConditionTrue,
+						LastTransitionTime: transitionTime,
+					},
+				},
+				OperatorVersion: version,
+			},
+		},
 	} {
 		t.Run(tt.name, func(t *testing.T) {
-
 			client := arofake.NewSimpleClientset(tt.objects...)
 
-			err := SetCondition(ctx, client, &tt.input, role)
+			err := SetCondition(ctx, client, tt.input, role)
 			if err != nil && tt.wantErr != nil {
 				t.Fatal(err.Error())
 			}
@@ -169,8 +230,8 @@ func TestIsConditions(t *testing.T) {
 	for _, tt := range []struct {
 		name       string
 		conditions []operatorv1.OperatorCondition
-		t          arov1alpha1.CondititionType
-		f          func([]operatorv1.OperatorCondition, arov1alpha1.CondititionType) bool
+		t          string
+		f          func([]operatorv1.OperatorCondition, string) bool
 		expect     bool
 	}{
 		{

```
github-actions[bot]: Please rebase pull request.

Patch:
diff --git a/go.mod b/go.mod
index 8efe7c637..a1d972bd0 100644
--- a/go.mod
+++ b/go.mod
@@ -24,7 +24,7 @@ require (
 	github.com/containers/storage v1.31.2 // indirect
 	github.com/coreos/go-oidc v2.2.1+incompatible
 	github.com/coreos/go-systemd/v22 v22.3.2
-	github.com/coreos/ignition/v2 v2.10.1 // indirect
+	github.com/coreos/ignition/v2 v2.10.1
 	github.com/coreos/vcontext v0.0.0-20210511154938-8fbad538d364 // indirect
 	github.com/davecgh/go-spew v1.1.1
 	github.com/docker/docker v20.10.6+incompatible // indirect
@@ -81,9 +81,9 @@ require (
 	github.com/openshift/library-go v0.0.0-20210521084623-7392ea9b02ca
 	github.com/openshift/machine-api-operator v0.2.1-0.20210516083017-bb9e0b5c1170
 	github.com/openshift/machine-config-operator v4.2.0-alpha.0.0.20190917115525-033375cbe820+incompatible
-	github.com/operator-framework/operator-sdk v1.7.2
 	github.com/pborman/uuid v1.2.1 // indirect
 	github.com/pires/go-proxyproto v0.5.0
+	github.com/pkg/errors v0.9.1
 	github.com/pquerna/cachecontrol v0.1.0 // indirect
 	github.com/prometheus/client_golang v1.10.0
 	github.com/prometheus/common v0.25.0
diff --git a/pkg/monitor/cluster/arooperatorconditions.go b/pkg/monitor/cluster/arooperatorconditions.go
index 3ebba037b..1c0b62696 100644
--- a/pkg/monitor/cluster/arooperatorconditions.go
+++ b/pkg/monitor/cluster/arooperatorconditions.go
@@ -6,17 +6,16 @@ package cluster
 import (
 	"context"
 
-	"github.com/operator-framework/operator-sdk/pkg/status"
+	operatorv1 "github.com/openshift/api/operator/v1"
 	"github.com/sirupsen/logrus"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 )
 
-var aroOperatorConditionsExpected = map[status.ConditionType]corev1.ConditionStatus{
-	arov1alpha1.InternetReachableFromMaster: corev1.ConditionTrue,
-	arov1alpha1.InternetReachableFromWorker: corev1.ConditionTrue,
+var aroOperatorConditionsExpected = map[string]operatorv1.ConditionStatus{
+	arov1alpha1.InternetReachableFromMaster: operatorv1.ConditionTrue,
+	arov1alpha1.InternetReachableFromWorker: operatorv1.ConditionTrue,
 }
 
 func (mon *Monitor) emitAroOperatorConditions(ctx context.Context) error {
@@ -35,7 +34,7 @@ func (mon *Monitor) emitAroOperatorConditions(ctx context.Context) error {
 			"type":   string(c.Type),
 		})
 
-		if mon.hourlyRun && c.Status == corev1.ConditionFalse {
+		if mon.hourlyRun && c.Status == operatorv1.ConditionFalse {
 			mon.log.WithFields(logrus.Fields{
 				"metric":  "arooperator.conditions",
 				"status":  c.Status,
diff --git a/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go b/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
index 98155c1d2..71034cf05 100644
--- a/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
+++ b/pkg/operator/apis/aro.openshift.io/v1alpha1/cluster_types.go
@@ -4,7 +4,7 @@ package v1alpha1
 // Licensed under the Apache License 2.0.
 
 import (
-	"github.com/operator-framework/operator-sdk/pkg/status"
+	operatorv1 "github.com/openshift/api/operator/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
@@ -15,22 +15,22 @@ const (
 	BannerDisabled       BannerContent = ""
 	BannerContactSupport BannerContent = "ContactSupport"
 
-	SingletonClusterName                             = "cluster"
-	InternetReachableFromMaster status.ConditionType = "InternetReachableFromMaster"
-	InternetReachableFromWorker status.ConditionType = "InternetReachableFromWorker"
-	MachineValid                status.ConditionType = "MachineValid"
-	ServicePrincipalValid       status.ConditionType = "ServicePrincipalValid"
+	SingletonClusterName        = "cluster"
+	InternetReachableFromMaster = "InternetReachableFromMaster"
+	InternetReachableFromWorker = "InternetReachableFromWorker"
+	MachineValid                = "MachineValid"
+	ServicePrincipalValid       = "ServicePrincipalValid"
 )
 
 // AllConditionTypes is a operator conditions currently in use, any condition not in this list is not
 // added to the operator.status.conditions list
-func AllConditionTypes() []status.ConditionType {
-	return []status.ConditionType{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
+func AllConditionTypes() []string {
+	return []string{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
 }
 
 // ClusterChecksTypes represents checks performed on the cluster to verify basic functionality
-func ClusterChecksTypes() []status.ConditionType {
-	return []status.ConditionType{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
+func ClusterChecksTypes() []string {
+	return []string{InternetReachableFromMaster, InternetReachableFromWorker, MachineValid, ServicePrincipalValid}
 }
 
 type GenevaLoggingSpec struct {
@@ -92,9 +92,9 @@ type Banner struct {
 
 // ClusterStatus defines the observed state of Cluster
 type ClusterStatus struct {
-	OperatorVersion   string            `json:"operatorVersion,omitempty"`
-	Conditions        status.Conditions `json:"conditions,omitempty"`
-	RedHatKeysPresent []string          `json:"redHatKeysPresent,omitempty"`
+	OperatorVersion   string                         `json:"operatorVersion,omitempty"`
+	Conditions        []operatorv1.OperatorCondition `json:"conditions,omitempty"`
+	RedHatKeysPresent []string                       `json:"redHatKeysPresent,omitempty"`
 }
 
 // +kubebuilder:object:root=true
diff --git a/pkg/operator/apis/aro.openshift.io/v1alpha1/zz_generated.deepcopy.go b/pkg/operator/apis/aro.openshift.io/v1alpha1/zz_generated.deepcopy.go
index 3fbe5303c..01d83bd83 100644
--- a/pkg/operator/apis/aro.openshift.io/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/operator/apis/aro.openshift.io/v1alpha1/zz_generated.deepcopy.go
@@ -5,7 +5,7 @@
 package v1alpha1
 
 import (
-	"github.com/operator-framework/operator-sdk/pkg/status"
+	v1 "github.com/openshift/api/operator/v1"
 	runtime "k8s.io/apimachinery/pkg/runtime"
 )
 
@@ -112,7 +112,7 @@ func (in *ClusterStatus) DeepCopyInto(out *ClusterStatus) {
 	*out = *in
 	if in.Conditions != nil {
 		in, out := &in.Conditions, &out.Conditions
-		*out = make(status.Conditions, len(*in))
+		*out = make([]v1.OperatorCondition, len(*in))
 		for i := range *in {
 			(*in)[i].DeepCopyInto(&(*out)[i])
 		}
diff --git a/pkg/operator/controllers/banner/banner.go b/pkg/operator/controllers/banner/banner.go
index 022fba583..4fbcce41c 100644
--- a/pkg/operator/controllers/banner/banner.go
+++ b/pkg/operator/controllers/banner/banner.go
@@ -24,7 +24,7 @@ func (r *Reconciler) reconcileBanner(ctx context.Context, instance *arov1alpha1.
 		}
 		return err
 	case arov1alpha1.BannerContactSupport:
-		banner := r.newBanner(fmt.Sprintf(TextContactSupport, instance.Spec.ResourceID))
+		banner := r.newBanner(TextContactSupport, instance.Spec.ResourceID)
 		_, err := r.consolecli.ConsoleV1().ConsoleNotifications().Get(ctx, BannerName, metav1.GetOptions{})
 		if err != nil && !kerrors.IsNotFound(err) {
 			return err
@@ -43,13 +43,13 @@ func (r *Reconciler) reconcileBanner(ctx context.Context, instance *arov1alpha1.
 	return fmt.Errorf("wrong banner setting '%s'", instance.Spec.Banner.Content)
 }
 
-func (r *Reconciler) newBanner(text string) *consolev1.ConsoleNotification {
+func (r *Reconciler) newBanner(text string, resourceID string) *consolev1.ConsoleNotification {
 	return &consolev1.ConsoleNotification{
 		ObjectMeta: metav1.ObjectMeta{
 			Name: BannerName,
 		},
 		Spec: consolev1.ConsoleNotificationSpec{
-			Text:            text,
+			Text:            fmt.Sprintf(TextContactSupport, resourceID),
 			Location:        consolev1.BannerTop,
 			Color:           "#000",
 			BackgroundColor: "#ff0",
diff --git a/pkg/operator/controllers/banner/banner_controller.go b/pkg/operator/controllers/banner/banner_controller.go
index a1e34e9ab..3654e81dd 100644
--- a/pkg/operator/controllers/banner/banner_controller.go
+++ b/pkg/operator/controllers/banner/banner_controller.go
@@ -61,14 +61,10 @@ func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
 		return o.GetName() == arov1alpha1.SingletonClusterName
 	})
 
-	aroBannerPredicate := predicate.NewPredicateFuncs(func(o client.Object) bool {
-		return o.GetName() == BannerName
-	})
-
 	return ctrl.NewControllerManagedBy(mgr).
 		For(&arov1alpha1.Cluster{}, builder.WithPredicates(aroClusterPredicate)).
 		// watching ConsoleNotifications in case a user edits it
-		Watches(&source.Kind{Type: &consolev1.ConsoleNotification{}}, &handler.EnqueueRequestForObject{}, builder.WithPredicates(aroBannerPredicate)).
+		Watches(&source.Kind{Type: &consolev1.ConsoleNotification{}}, &handler.EnqueueRequestForObject{}).
 		Named(controllers.BannerControllerName).
 		Complete(r)
 }
diff --git a/pkg/operator/controllers/checker/internetchecker.go b/pkg/operator/controllers/checker/internetchecker.go
index cffd7549c..531739cf5 100644
--- a/pkg/operator/controllers/checker/internetchecker.go
+++ b/pkg/operator/controllers/checker/internetchecker.go
@@ -10,15 +10,14 @@ import (
 	"strings"
 	"time"
 
-	"github.com/operator-framework/operator-sdk/pkg/status"
+	operatorv1 "github.com/openshift/api/operator/v1"
 	"github.com/sirupsen/logrus"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 
 	"github.com/Azure/ARO-RP/pkg/operator"
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
-	"github.com/Azure/ARO-RP/pkg/operator/controllers"
+	"github.com/Azure/ARO-RP/pkg/util/conditions"
 )
 
 // InternetChecker reconciles a Cluster object
@@ -97,26 +96,26 @@ func (r *InternetChecker) Check(ctx context.Context) error {
 		}
 	}
 
-	var condition *status.Condition
+	var condition *operatorv1.OperatorCondition
 
 	if checkFailed {
-		condition = &status.Condition{
+		condition = &operatorv1.OperatorCondition{
 			Type:    r.conditionType(),
-			Status:  corev1.ConditionFalse,
+			Status:  operatorv1.ConditionFalse,
 			Message: sb.String(),
 			Reason:  "CheckFailed",
 		}
 	} else {
-		condition = &status.Condition{
+		condition = &operatorv1.OperatorCondition{
 			Type:    r.conditionType(),
-			Status:  corev1.ConditionTrue,
+			Status:  operatorv1.ConditionTrue,
 			Message: "Outgoing connection successful",
 			Reason:  "CheckDone",
 		}
 
 	}
 
-	err = controllers.SetCondition(ctx, r.arocli, condition, r.role)
+	err = conditions.SetCondition(ctx, r.arocli, condition, r.role)
 	if err != nil {
 		return err
 	}
@@ -165,7 +164,7 @@ func (r *InternetChecker) checkOnce(client simpleHTTPClient, url string, timeout
 	return nil
 }
 
-func (r *InternetChecker) conditionType() (ctype status.ConditionType) {
+func (r *InternetChecker) conditionType() (ctype string) {
 	switch r.role {
 	case operator.RoleMaster:
 		return arov1alpha1.InternetReachableFromMaster
diff --git a/pkg/operator/controllers/checker/serviceprincipalchecker.go b/pkg/operator/controllers/checker/serviceprincipalchecker.go
index 458327820..bd7fb3575 100644
--- a/pkg/operator/controllers/checker/serviceprincipalchecker.go
+++ b/pkg/operator/controllers/checker/serviceprincipalchecker.go
@@ -6,10 +6,9 @@ package checker
 import (
 	"context"
 
+	operatorv1 "github.com/openshift/api/operator/v1"
 	maoclient "github.com/openshift/machine-api-operator/pkg/generated/clientset/versioned"
-	"github.com/operator-framework/operator-sdk/pkg/status"
 	"github.com/sirupsen/logrus"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/kubernetes"
 
@@ -17,10 +16,10 @@ import (
 	"github.com/Azure/ARO-RP/pkg/api/validate/dynamic"
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
-	"github.com/Azure/ARO-RP/pkg/operator/controllers"
 	"github.com/Azure/ARO-RP/pkg/util/aad"
 	"github.com/Azure/ARO-RP/pkg/util/azureclient"
 	"github.com/Azure/ARO-RP/pkg/util/clusterauthorizer"
+	"github.com/Azure/ARO-RP/pkg/util/conditions"
 )
 
 type ServicePrincipalChecker struct {
@@ -48,9 +47,9 @@ func (r *ServicePrincipalChecker) Name() string {
 }
 
 func (r *ServicePrincipalChecker) Check(ctx context.Context) error {
-	cond := &status.Condition{
+	cond := &operatorv1.OperatorCondition{
 		Type:    arov1alpha1.ServicePrincipalValid,
-		Status:  corev1.ConditionTrue,
+		Status:  operatorv1.ConditionTrue,
 		Message: "service principal is valid",
 		Reason:  "CheckDone",
 	}
@@ -85,11 +84,11 @@ func (r *ServicePrincipalChecker) Check(ctx context.Context) error {
 		updateFailedCondition(cond, err)
 	}
 
-	return controllers.SetCondition(ctx, r.arocli, cond, r.role)
+	return conditions.SetCondition(ctx, r.arocli, cond, r.role)
 }
 
-func updateFailedCondition(cond *status.Condition, err error) {
-	cond.Status = corev1.ConditionFalse
+func updateFailedCondition(cond *operatorv1.OperatorCondition, err error) {
+	cond.Status = operatorv1.ConditionFalse
 	if tErr, ok := err.(*api.CloudError); ok {
 		cond.Message = tErr.Message
 	} else {
diff --git a/pkg/operator/controllers/condition.go b/pkg/operator/controllers/condition.go
deleted file mode 100644
index dac17e573..000000000
--- a/pkg/operator/controllers/condition.go
+++ /dev/null
@@ -1,68 +0,0 @@
-package controllers
-
-// Copyright (c) Microsoft Corporation.
-// Licensed under the Apache License 2.0.
-
-import (
-	"context"
-
-	"github.com/operator-framework/operator-sdk/pkg/status"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/client-go/util/retry"
-
-	"github.com/Azure/ARO-RP/pkg/operator"
-	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
-	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
-	"github.com/Azure/ARO-RP/pkg/util/version"
-)
-
-func SetCondition(ctx context.Context, arocli aroclient.Interface, cond *status.Condition, role string) error {
-	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
-		cluster, err := arocli.AroV1alpha1().Clusters().Get(ctx, arov1alpha1.SingletonClusterName, metav1.GetOptions{})
-		if err != nil {
-			return err
-		}
-
-		changed := cluster.Status.Conditions.SetCondition(*cond)
-
-		if cleanStaleConditions(cluster, role) {
-			changed = true
-		}
-
-		if !changed {
-			return nil
-		}
-
-		_, err = arocli.AroV1alpha1().Clusters().UpdateStatus(ctx, cluster, metav1.UpdateOptions{})
-		return err
-	})
-}
-
-// cleanStaleConditions ensures that conditions no longer in use as defined by older operators
-// are always removed from the cluster.status.conditions
-func cleanStaleConditions(cluster *arov1alpha1.Cluster, role string) (changed bool) {
-	conditions := make(status.Conditions, 0, len(cluster.Status.Conditions))
-
-	// cleanup any old conditions
-	current := map[status.ConditionType]bool{}
-	for _, ct := range arov1alpha1.AllConditionTypes() {
-		current[ct] = true
-	}
-
-	for _, cond := range cluster.Status.Conditions {
-		if _, ok := current[cond.Type]; ok {
-			conditions = append(conditions, cond)
-		} else {
-			changed = true
-		}
-	}
-
-	cluster.Status.Conditions = conditions
-
-	if role == operator.RoleMaster && cluster.Status.OperatorVersion != version.GitCommit {
-		cluster.Status.OperatorVersion = version.GitCommit
-		changed = true
-	}
-
-	return
-}
diff --git a/pkg/operator/controllers/machine/machine_controller.go b/pkg/operator/controllers/machine/machine_controller.go
index 268c0fd8e..baa45eb78 100644
--- a/pkg/operator/controllers/machine/machine_controller.go
+++ b/pkg/operator/controllers/machine/machine_controller.go
@@ -7,17 +7,17 @@ import (
 	"context"
 	"strings"
 
+	operatorv1 "github.com/openshift/api/operator/v1"
 	machinev1beta1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1"
 	maoclient "github.com/openshift/machine-api-operator/pkg/generated/clientset/versioned"
-	"github.com/operator-framework/operator-sdk/pkg/status"
 	"github.com/sirupsen/logrus"
-	corev1 "k8s.io/api/core/v1"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/reconcile"
 
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
 	"github.com/Azure/ARO-RP/pkg/operator/controllers"
+	"github.com/Azure/ARO-RP/pkg/util/conditions"
 	_ "github.com/Azure/ARO-RP/pkg/util/scheme"
 )
 
@@ -43,16 +43,16 @@ func NewReconciler(log *logrus.Entry, arocli aroclient.Interface, maocli maoclie
 
 func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.Result, error) {
 	// Update cluster object's status.
-	cond := &status.Condition{
+	cond := &operatorv1.OperatorCondition{
 		Type:    arov1alpha1.MachineValid,
-		Status:  corev1.ConditionTrue,
+		Status:  operatorv1.ConditionTrue,
 		Message: "All machines valid",
 		Reason:  "CheckDone",
 	}
 
 	errs := r.checkMachines(ctx)
 	if len(errs) > 0 {
-		cond.Status = corev1.ConditionFalse
+		cond.Status = operatorv1.ConditionFalse
 		cond.Reason = "CheckFailed"
 
 		var sb strings.Builder
@@ -63,7 +63,7 @@ func (r *Reconciler) Reconcile(ctx context.Context, request ctrl.Request) (ctrl.
 		cond.Message = sb.String()
 	}
 
-	return reconcile.Result{}, controllers.SetCondition(ctx, r.arocli, cond, r.role)
+	return reconcile.Result{}, conditions.SetCondition(ctx, r.arocli, cond, r.role)
 }
 
 func (r *Reconciler) SetupWithManager(mgr ctrl.Manager) error {
diff --git a/pkg/operator/deploy/bindata.go b/pkg/operator/deploy/bindata.go
index 2769b9cfb..8a7221db2 100644
--- a/pkg/operator/deploy/bindata.go
+++ b/pkg/operator/deploy/bindata.go
@@ -86,7 +86,7 @@ func (fi bindataFileInfo) Sys() interface{} {
 	return nil
 }
 
-var _aroOpenshiftIo_clustersYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xdc\x59\xcd\x72\xdb\xc8\x11\xbe\xf3\x29\xba\x94\x83\x0f\x11\xa9\x75\xa5\x52\x95\xf0\xa6\x95\x6c\x87\xe5\xb5\xac\x92\x54\xde\xc3\x6a\x0f\x8d\x41\x13\xec\x68\x30\x83\xed\x69\x50\xa2\x53\x79\xf7\xd4\x0c\x00\xfe\x09\xa0\x44\xab\xf6\x12\x5c\x24\x0c\x66\xfa\xbf\xbf\x9e\x6e\x8e\xc6\xe3\xf1\x08\x2b\xfe\x46\x12\xd8\xbb\x29\x60\xc5\xf4\xa4\xe4\xe2\x5b\x98\x3c\xfc\x23\x4c\xd8\x9f\x2d\xdf\x8f\x1e\xd8\xe5\x53\xb8\xa8\x83\xfa\xf2\x86\x82\xaf\xc5\xd0\x25\xcd\xd9\xb1\xb2\x77\xa3\x92\x14\x73\x54\x9c\x8e\x00\xd0\x39\xaf\x18\x97\x43\x7c\x05\x30\xde\xa9\x78\x6b\x49\xc6\x05\xb9\xc9\x43\x9d\x51\x56\xb3\xcd\x49\x12\xf1\x8e\xf5\xf2\xa7\xc9\xdf\x27\x3f\x8d\x00\x8c\x50\x3a\x7e\xc7\x25\x05\xc5\xb2\x9a\x82\xab\xad\x1d\x01\x38\x2c\x69\x0a\xc6\xd6\x41\x49\xc2\x04\xc5\x4f\x7c\x45\x2e\x2c\x78\xae\x13\xf6\xa3\x50\x91\x89\x3c\x0b\xf1\x75\x35\x85\x67\xdf\x1b\x0a\xad\x58\xad\x4a\x0d\xb1\xb4\x62\x39\xe8\xe7\xed\xd5\x5f\x38\x68\xfa\x52\xd9\x5a\xd0\x6e\x58\xa7\xc5\xc0\xae\xa8\x2d\xca\x7a\x79\x04\x10\x8c\xaf\x68\x9b\x6a\xab\x5e\xe2\x39\x6e\x15\x58\xbe\x47\x5b\x2d\xf0\x7d\x43\xc5\x2c\xa8\xc4\x46\x24\x80\x28\xee\xf9\xf5\xec\xdb\xdf\x6e\x77\x96\x01\x72\x0a\x46\xb8\xd2\x64\xaa\x96\x3c\x70\x00\x5d\x10\x34\x7b\x61\xee\x25\xbd\x76\x42\xc2\xf9\xf5\x6c\x7d\xbe\x12\x5f\x91\x28\x77\xda\x37\xcf\x96\xeb\xb7\x56\xf7\xb8\xbd\x8b\x02\x35\xbb\x20\x8f\x3e\xa7\x86\x6d\xab\x1a\xe5\xad\x0e\xe0\xe7\xa0\x0b\x0e\x20\x54\x09\x05\x72\x4d\x14\xec\x10\x86\xb8\x09\x1d\xf8\xec\xdf\x64\x74\x02\xb7\x24\x91\x0c\x84\x85\xaf\x6d\x1e\x43\x65\x49\xa2\x20\x64\x7c\xe1\xf8\xfb\x9a\x76\x00\xf5\x89\xa9\x45\xa5\xd6\x29\x9b\x87\x9d\x92\x38\xb4\xb0\x44\x5b\xd3\x29\xa0\xcb\xa1\xc4\x15\x08\x45\x2e\x50\xbb\x2d\x7a\x69\x4b\x98\xc0\x17\x2f\x04\xec\xe6\x7e\x0a\x0b\xd5\x2a\x4c\xcf\xce\x0a\xd6\x2e\xe4\x8d\x2f\xcb\xda\xb1\xae\xce\x52\xf4\x72\x56\xab\x97\x70\x96\xd3\x92\xec\x59\xe0\x62\x8c\x62\x16\xac\x64\xb4\x16\x3a\xc3\x8a\xc7\x49\x74\x97\xc2\x7e\x52\xe6\x7f\x91\x36\x49\xc2\xbb\x1d\x59\x75\x15\xc3\x23\xa8\xb0\x2b\xb6\x3e\xa4\x58\x3c\xe0\x81\x18\x95\xd1\xdb\xd8\x1e\x6d\xb4\xd8\x18\x3a\x2e\x45\xeb\xdc\x7c\xb8\xbd\x83\x8e\x75\x72\xc6\xbe\xf5\x93\xdd\x37\x07\xc3\xc6\x05\xd1\x60\xec\xe6\x24\x8d\x13\xe7\xe2\xcb\x44\x93\x5c\x5e\x79\x76\xda\xc6\x16\x93\xdb\x37\x7f\xa8\xb3\x92\x35\xfa\xfd\x8f\x9a\x82\x46\x5f\x4d\xe0\x22\xe1\x00\x64\x04\x75\x95\xa3\x52\x3e\x81\x99\x83\x0b\x2c\xc9\x5e\x60\xa0\x3f\xdd\x01\xd1\xd2\x61\x1c\x0d\xfb\x3a\x17\x6c\x43\xd8\xfe\xe6\xc6\x6a\x5b\x1f\x3a\xa0\x19\xf0\x57\x9b\x9f\xb7\x15\x99\x9d\x8c\xc9\x29\xb0\xc4\x98\x56\x54\x8a\x99\xb0\x8d\x3e\xdd\xd3\x9f\xa9\xf1\x41\x23\x97\xbe\x44\x76\xfb\x1f\x06\x95\x82\x26\xc7\x67\x4e\x67\xd7\xc7\x1d\xda\xb2\x6e\x2f\x42\x6c\xce\xc7\xe4\x2b\xf6\x74\x00\xc0\xef\x1f\xdc\x92\xc5\xbb\x92\x9c\x1e\xc5\x3a\x43\xe7\x48\x9e\x1f\xd9\xb1\xf0\xcf\x69\xd3\xda\xb8\x3c\x07\xec\xd6\x5a\x28\xc9\x28\xfe\xf7\xe8\x3a\xe0\x30\xa9\x76\x3d\x93\xf3\x90\xbd\xa1\x2d\x5e\xbd\x1a\xbc\xa0\xc5\x60\xe8\x24\x55\x8e\xf7\xe2\x9c\x30\xfa\xa2\x47\xc6\x1d\xbb\x7c\x6c\xb7\xed\x84\xde\xf9\xcd\xd7\x58\x58\x04\xd5\x4b\x47\x08\x8a\x08\xa4\x47\x1a\x23\xc2\xa8\x33\x6c\xe9\xdc\x92\xe8\xaf\x94\x2d\xbc\x7f\x38\x64\x9a\xcc\x7b\x4b\xb8\x5f\x02\x76\x48\xfd\x3c\xe0\xee\xa3\x88\x5c\x5e\xdd\x7e\xc1\xf0\xc7\x1b\xa9\x7c\x22\x47\x4b\xfc\xc5\x17\x05\xbb\xe2\x8d\xb4\xbe\xa0\x59\xb0\xa3\x5b\x3a\x18\x3a\xaf\x21\xe4\x1d\xab\x8f\x11\x71\xe1\xdd\x9c\xdf\x2a\xd7\xd5\xed\xa7\x5e\xd7\x1e\x43\xc2\xe7\x74\x29\xc8\x6f\x77\xdb\x75\x6d\xed\x2d\x19\x79\xb3\x91\x6e\x7c\xad\xf4\x91\x9f\xde\x48\xe6\x57\x2f\x0f\x28\xbe\x76\x79\xb8\x58\x5f\x5a\x7f\x84\xe6\x81\xdc\x8f\x79\xf7\x88\xab\x06\xc8\x7b\x5c\xc1\x4a\x65\xaf\x87\x5e\x01\x36\x28\x82\xab\x7e\x7e\xd7\xc2\x4b\x54\xfa\xd0\x16\xf3\x23\xcb\x41\x71\x38\x33\x5e\x84\xd0\x39\x17\x83\x55\x24\x11\x40\x8d\x37\xb8\x29\xbc\xfb\xed\xa7\xf1\x3f\x7f\xff\xeb\xa4\xf9\xf3\xee\x78\xd4\x05\x28\xd7\x09\xf3\xe9\xe2\xf6\xdc\x18\x5f\x0f\xc1\x37\xb9\xba\xec\xff\x32\x8e\x98\xd9\x35\x01\xbe\x08\xb3\xab\xbb\x57\xed\xbb\xbe\xf9\x7a\xf9\xaa\x8d\x6f\x56\xec\x60\x75\x7d\x49\xb9\x4b\xc6\xc2\xf9\xa0\x6c\xc2\xb5\xf8\x7c\x60\xd7\xdd\xf3\x8b\x76\xf7\xe9\x02\x3f\x22\xcb\x1c\x9f\xde\xac\xc7\x55\x6c\xc8\x2a\x34\xf4\x7f\xe0\xa2\x03\x59\xcf\x6e\x2e\x38\xcb\x8f\x4a\x3a\x76\x85\x50\x08\x47\xa6\x6a\xd3\x0b\x91\x5e\x2c\xc8\x3c\xf4\xa1\xd7\xe1\x64\xad\xc5\x0e\xd4\x87\x41\x60\x7a\x41\xa0\xed\x0d\x7d\x00\x75\xd0\x6e\xd6\x9b\xd4\x44\x1e\x65\x82\xae\x01\xea\xb3\xf7\xce\x6d\xa9\x9b\x64\xcc\x2e\xbb\x56\xfa\xfc\x7b\xbc\x1b\x6d\x08\x34\x3d\x2d\x6d\x75\xf8\xaf\x96\x62\xe9\x48\x8f\xf2\xf8\x50\xaf\xa1\xa8\x75\x78\x45\xb7\x91\xf6\xed\xf4\x1b\x3e\x0b\xb1\xb9\xfb\xe1\x86\xc3\x78\x97\xf3\xd6\x24\x67\x58\x84\xf5\xc6\xb6\x4b\x25\x4d\xdc\xba\x65\x60\x17\x14\x9d\xa1\x30\x79\x7d\xc1\xdb\xe1\x70\xb2\xa1\xb5\x69\x5e\x9b\x49\x42\xd4\x31\x05\xc9\xce\x6c\xe1\x5d\x7f\x02\x27\x4b\x4c\xb6\x05\x46\xa1\x78\x6a\x3d\xf6\x82\x92\xcc\x02\x1d\x87\x32\xe5\x92\xcb\x29\x8f\x3d\x44\x6c\x64\x03\xf5\x63\xe5\xe3\x82\x5c\xdb\xe0\x29\xb2\x0d\x6b\x41\x36\xa2\x45\x2e\xb1\x1f\x46\xa8\x84\xbd\x30\x3c\xb8\xd8\x9c\x78\x81\xc7\xd8\xae\xf4\x92\x4d\xfb\xab\xca\xae\x22\x7f\xb4\x76\x63\xc5\xc4\x00\x0a\x5e\x92\x83\xcf\xec\xf2\x09\xdc\xbb\x6d\x9d\x9a\x1e\xa8\x97\x68\x46\x80\x79\xab\x13\x3d\x55\x96\x0d\xab\x5d\x35\x93\x97\xd5\x56\x2c\x80\x2e\x50\xa3\xca\x12\xd2\x3c\xc5\xf8\xb2\xf2\x2e\x5a\xbd\x97\xac\x49\x66\xcc\x7c\xad\x20\xa8\x8b\x34\x45\x40\x97\x46\x02\x2c\xcd\x78\xc2\x07\xda\xa1\x9f\x6c\x9a\x26\x0e\x32\x60\xd7\x34\x83\xf0\x89\xda\x96\x2d\xc3\x04\xbe\x3a\x43\x6d\xa4\xe7\xa7\xc9\xf2\x25\xa1\x8b\x6c\x92\x61\xd6\x96\x18\x10\xd5\x41\x3b\x9a\x88\x8e\x2e\x28\x07\x94\x8c\x55\x50\xd8\xae\x60\x0c\x1c\xbf\x19\x5f\x52\x80\x0a\x45\x3b\x0c\x38\xbf\x9e\xa5\xd1\x52\x2f\xd1\x05\x36\x29\x17\xb0\x24\xc8\xd0\x3c\x3c\xa2\xe4\x61\x9c\x4c\x37\xf7\xd2\xbc\x45\x1b\xa2\x72\xc6\x96\x35\x99\xdc\x90\xb8\xe8\xcc\x5e\x92\xe8\x56\xad\xf2\x7b\x52\x4c\x4e\x7a\xf6\x1f\x86\x75\x00\x8b\x41\xef\x04\x5d\xe0\x6e\xb6\x3a\x84\xe5\x73\x2f\x25\xea\x14\x72\x54\x1a\x2b\x97\xf4\xa3\x98\x5f\x52\x08\x58\x0c\xf2\x79\xf1\xbc\x10\x86\xa1\x1b\xe3\x10\x00\xdd\xa4\x33\x11\x85\xf6\x92\x17\xc1\x3b\x1a\x3f\x7a\xc9\x4f\x37\x53\xa8\x01\xd2\xb0\x37\xc2\x5c\x57\x01\x54\x2a\xbc\xac\xe2\xbb\xc1\x3a\xd0\xfa\x43\x2d\x42\x4e\x5b\xac\x7e\x8e\x71\xdd\x33\xd3\x1e\xc9\x22\xac\x00\xbb\x14\x0f\x1c\x69\xd6\x5a\xd5\x7a\x0a\xa1\x36\x0b\xc0\x90\xe4\xb6\xec\x86\x85\x7d\xa8\x33\x32\x6a\xa1\x88\xa8\xdb\x1e\x8e\x71\xc7\x0e\x42\x5d\x96\x28\xfc\x3d\xa5\x86\x69\xc4\x6c\xf1\x23\x29\x30\x28\xeb\x8b\xce\xe9\x2b\x4b\x47\x1c\x4f\x1b\x5e\xe3\xd9\x0d\xf0\xdf\xad\x2a\xea\xea\x74\x3c\xbe\x36\xfe\xba\x32\x0c\x25\x67\x7c\xe2\xc1\x55\xc5\x06\xad\x5d\x45\x88\xe8\x42\x20\x87\x18\x13\x11\x88\xc3\xc2\x8b\x42\xb5\x90\x34\x9e\xdc\x06\xd4\x41\xa2\x69\xc8\xd8\x4d\x9c\xd8\xe5\x1c\x23\xa4\xad\xb6\xdc\x94\x84\xfb\x13\xcc\x5c\xcc\x28\x3b\x56\xa9\xe9\xfe\x04\x2a\x6f\x51\x58\x57\xc3\x61\xf2\xd1\x0b\xd0\x13\x96\x95\xa5\x53\xe0\x7d\x2d\x3b\x3e\xa1\xa9\x3b\x18\x09\xb2\x59\x35\x91\xb5\x44\xcb\xf9\xe9\xb0\xc0\x49\x22\x0e\x90\xf6\xdd\x9f\x80\xc1\x90\x8c\x5a\x89\xcf\x30\x8b\xa5\x66\x11\x0b\x95\x94\xa7\x10\xfc\x2e\xe3\x41\xa2\x9b\x89\x5b\xb4\x2e\xe5\x70\x7f\x32\x73\x2d\x83\x5e\xac\x82\x97\x23\xa4\x29\x1c\xd4\x73\x7f\x8a\x37\xf5\x26\xf8\x7a\x3f\x45\xba\x83\xdd\x73\xef\x1d\xf3\xd0\xe5\xb4\x1b\x9b\xbd\x30\x03\x1d\xb8\x86\xe6\xff\x42\xfd\x4c\xab\x70\xdd\x60\xc9\x9f\xdd\xec\xf7\xea\xf8\x6c\xb1\xb9\x12\x4e\x21\x46\x63\xb3\xa0\x5e\x22\x4c\x6f\xad\xd4\xd9\xfa\xf7\x8b\x4e\xba\x36\xdf\xe1\x3f\xff\x1d\x6d\x52\x1f\x8d\xa1\x4a\x29\xbf\xda\xff\x59\xed\xa4\x71\x7b\xf7\xbb\x59\x7a\xdd\xba\x4d\xc2\x6f\xbf\x8f\x1a\xc6\x94\x7f\xeb\x7e\x21\x8b\x8b\xff\x0b\x00\x00\xff\xff\xf7\xab\x22\x12\x91\x1c\x00\x00")
+var _aroOpenshiftIo_clustersYaml = []byte("\x1f\x8b\x08\x00\x00\x00\x00\x00\x00\xff\xdc\x58\xcd\x72\xdb\x36\x10\xbe\xeb\x29\x76\xd2\x83\x0f\xad\xa8\x64\x3a\x9d\x69\x75\x73\xa4\x24\xd5\x24\x71\x34\x96\x27\x39\x64\x72\x58\x81\x4b\x6a\x63\x12\x60\x80\x85\x12\xbb\xd3\x77\xef\x80\x3f\x12\x25\x93\xb2\x64\x4d\x2e\xd5\xc5\xe6\x62\xb1\x7f\xdf\xee\x02\x8b\xc1\x70\x38\x1c\x60\xc1\x1f\xc9\x3a\x36\x7a\x0c\x58\x30\xfd\x10\xd2\xe1\xcb\x45\xb7\x7f\xba\x88\xcd\x68\xfd\x62\x70\xcb\x3a\x1e\xc3\xc4\x3b\x31\xf9\x35\x39\xe3\xad\xa2\x29\x25\xac\x59\xd8\xe8\x41\x4e\x82\x31\x0a\x8e\x07\x00\xa8\xb5\x11\x0c\x64\x17\x3e\x01\x94\xd1\x62\x4d\x96\x91\x1d\xa6\xa4\xa3\x5b\xbf\xa4\xa5\xe7\x2c\x26\x5b\x0a\x6f\x54\xaf\x9f\x47\x7f\x44\xcf\x07\x00\xca\x52\xb9\xfd\x86\x73\x72\x82\x79\x31\x06\xed\xb3\x6c\x00\xa0\x31\xa7\x31\xa8\xcc\x3b\x21\xeb\x22\xb4\x26\x32\x05\x69\xb7\xe2\x44\x22\x36\x03\x57\x90\x0a\x3a\x53\x6b\x7c\x31\x86\x07\xeb\x95\x84\xda\xac\xda\xa5\x4a\x58\x49\xc9\xd8\xc9\xdb\x36\xf5\x1d\x3b\x29\x57\x8a\xcc\x5b\xcc\xb6\xaa\x4b\xa2\x63\x9d\xfa\x0c\xed\x86\x3c\x00\x70\xca\x14\xd4\x96\x5a\xbb\x57\xea\x1c\xd6\x0e\xac\x5f\x60\x56\xac\xf0\x45\x25\x45\xad\x28\xc7\xca\x24\x80\x60\xee\xe5\x7c\xf6\xf1\xf7\xc5\x0e\x19\x20\x26\xa7\x2c\x17\x52\x86\xaa\x16\x0f\xec\x40\x56\x04\x15\x2f\x24\xc6\x96\x9f\x8d\x91\x70\x39\x9f\x6d\xf6\x17\xd6\x14\x64\x85\x1b\xef\xab\x5f\x0b\xfa\x16\x75\x4f\xdb\x45\x30\xa8\xe2\x82\x38\x60\x4e\x95\xda\xda\x35\x8a\x6b\x1f\xc0\x24\x20\x2b\x76\x60\xa9\xb0\xe4\x48\x57\x59\xb0\x23\x18\x02\x13\x6a\x30\xcb\xaf\xa4\x24\x82\x05\xd9\x20\x06\xdc\xca\xf8\x2c\x0e\xa9\xb2\x26\x2b\x60\x49\x99\x54\xf3\xfd\x46\xb6\x03\x31\xa5\xd2\x0c\x85\x6a\x50\xb6\x3f\xd6\x42\x56\x63\x06\x6b\xcc\x3c\xfd\x06\xa8\x63\xc8\xf1\x0e\x2c\x05\x2d\xe0\x75\x4b\x5e\xc9\xe2\x22\x78\x6f\x2c\x01\xeb\xc4\x8c\x61\x25\x52\xb8\xf1\x68\x94\xb2\x34\x29\xaf\x4c\x9e\x7b\xcd\x72\x37\x2a\xb3\x97\x97\x5e\x8c\x75\xa3\x98\xd6\x94\x8d\x1c\xa7\x43\xb4\x6a\xc5\x42\x4a\xbc\xa5\x11\x16\x3c\x2c\x4d\xd7\x65\xda\x47\x79\xfc\x8b\xad\x8b\xc4\x5d\xec\xd8\x2a\x77\x21\x3d\x9c\x58\xd6\x69\x6b\xa1\xcc\xc5\x03\x08\x84\xac\x0c\x68\x63\xbd\xb5\xf2\x62\x1b\xe8\x40\x0a\xd1\xb9\x7e\xb5\xb8\x81\x46\x75\x09\xc6\x7e\xf4\xcb\xb8\x6f\x37\xba\x2d\x04\x21\x60\xac\x13\xb2\x15\x88\x89\x35\x79\x29\x93\x74\x5c\x18\xd6\x52\xe7\x16\x93\xde\x0f\xbf\xf3\xcb\x9c\x25\xe0\xfe\xcd\x93\x93\x80\x55\x04\x93\xb2\x0f\xc0\x92\xc0\x17\x31\x0a\xc5\x11\xcc\x34\x4c\x30\xa7\x6c\x82\x8e\x7e\x3a\x00\x21\xd2\x6e\x18\x02\x7b\x1c\x04\xed\x16\xb6\xcf\x5c\x45\xad\xb5\xd0\x34\x9a\x1e\xbc\xea\xfa\x5c\x14\xa4\x76\x2a\x26\x26\xc7\x36\xe4\xb4\xa0\x50\xa8\x84\x76\xf7\x69\x7e\xdd\x95\x1a\x7e\xa8\xec\xd4\xe4\xc8\x7a\x7f\xa1\xd7\x29\xa8\x6a\x7c\xa6\x65\x36\x3f\x6d\x53\x2b\xba\x9d\x1d\x62\xbb\x3f\x14\x5f\xba\xe7\x03\x00\xde\xbf\xd2\x6b\xb6\x46\xe7\xa4\xe5\x24\xd5\x4b\xd4\x9a\xec\xc3\x2d\x3b\x11\x7e\x59\x32\x6d\x82\xcb\x09\x60\x43\xab\x5b\xc9\x92\xc2\x7f\xdf\x75\xd3\x38\x54\x79\x76\x3d\xb0\xf3\x50\xbc\xa1\x3e\xbc\x3a\x3d\x78\xc4\x8b\xde\xd4\x29\x5d\x39\x1d\xc5\x84\x30\x60\xd1\x61\xe3\x4e\x5c\x5e\xd7\x6c\x3b\xa9\x77\x79\xfd\x21\x1c\x2c\x16\xc5\xd8\x46\x10\xa4\xa1\x91\x9e\x18\x8c\xd0\x46\xb5\xe2\x8c\x2e\x33\xb2\xf2\x89\x96\x2b\x63\x6e\x0f\x85\x66\x69\x4c\x46\xb8\x7f\x04\xec\x88\x7a\xd9\x03\xf7\x49\x42\xa6\x57\x8b\xf7\xe8\xbe\x9d\x29\xe5\x0d\x69\x5a\xe3\x3b\x93\xa6\xac\xd3\x33\x65\xbd\x47\xb5\x62\x4d\x0b\x3a\x98\x3a\xc7\x08\x32\x9a\xc5\x84\x8c\x98\x18\x9d\xf0\xb9\x76\x5d\x2d\xde\x74\x42\x7b\x8a\x08\x13\xd3\xd4\x22\x9f\x0f\xdb\xdc\x67\xd9\x82\x94\x3d\x3b\x48\xd7\xc6\x0b\xbd\xe6\x1f\x67\x8a\xf9\x64\xec\x2d\x5a\xe3\x75\xec\x26\x9b\x4b\xeb\x53\x64\x1e\xa8\xfd\x50\x77\xdf\xf1\xae\x6a\xe4\x1d\x50\xb0\x50\xde\x89\xd0\x11\xcd\x06\xad\xc5\xbb\x6e\x7d\x73\xcb\x6b\x14\x7a\x55\x1f\xe6\x27\x1e\x07\xe9\xe1\xca\x78\xb4\x85\x26\x9c\xf6\x9e\x22\xa5\x00\x94\x70\x83\x1b\xc3\xc5\xe7\xe7\xc3\xbf\xbe\xfc\x1a\x55\x7f\x2e\x4e\xef\xba\x00\xf9\xa6\x60\xde\x4c\x16\x97\x4a\x19\xdf\xd7\xbe\x49\xfb\xbc\x7b\x65\x18\x7a\x66\x33\x04\x98\xd4\xcd\xae\x6e\x8e\xe2\x9b\x5f\x7f\x98\x1e\xc5\x78\xb6\x63\x07\x4f\xd7\xc7\x9c\x9b\x32\xa6\xda\x38\x61\xe5\xe6\xd6\xc4\x3d\x5c\x37\x0f\x2f\xda\xcd\xd2\x04\x5f\x23\xdb\x04\x7f\x9c\xed\xc7\x55\x18\xc8\x0a\x54\xf4\x3f\x80\xe8\x40\xd5\xb3\x4e\x2c\xce\xe2\x93\x8a\x8e\x75\x6a\xc9\xb9\x13\x4b\xb5\x9a\x85\x48\x26\x2b\x52\xb7\x5d\xdd\xeb\x70\xb1\x7a\x9b\xf5\x9c\x0f\xbd\x8d\xe9\x11\x83\xda\x0c\x5d\x0d\xea\x60\xdc\x32\xa3\xca\x21\xf2\xa4\x10\x34\x03\x50\x57\xbc\x77\x6e\x4b\xcd\x4b\xc6\x6c\xda\x8c\xd2\x97\xf7\xe1\x6e\xb4\x15\x50\xcd\xb4\xd4\x9a\xf0\x8f\xb6\x62\xad\x49\x4e\x42\xbc\x6f\xd6\x10\x14\xef\x8e\x98\x36\x4a\xbe\x9d\x79\xc3\x2c\x5d\x18\xee\x9e\x3c\x70\x28\xa3\x63\x6e\xbd\xe4\xb4\x7f\xbd\xe9\xb0\x63\xdb\x87\xfa\xda\x39\x69\x24\x85\x40\x7f\xf5\xae\x9a\x26\x9d\xa0\x8e\xd1\xc6\x5b\x45\x90\x30\x65\xb1\x8b\x3a\xe4\x1e\xce\x5b\x80\x0c\x9d\xdc\x58\xd4\x8e\x9b\xc7\xa3\xbe\x64\x4d\x8c\xcd\x51\xc6\x10\xc6\xd2\xa1\x70\x4e\x4f\x4d\xea\x9c\x9c\xc3\xb4\x57\xcf\xa3\xfb\x2d\xa1\xeb\x3b\x12\x8f\xd8\xde\x95\x19\x27\x6c\x2f\x19\x9e\xb6\xf9\x40\xc5\x1e\x2a\xf5\x66\x08\x79\x64\xa2\xec\x29\xea\xf8\x6f\x94\xb7\x74\xe7\xe6\xd5\xd3\xc5\xcf\xbe\x3a\x75\xfa\xf8\x80\x58\x15\xd8\x18\xc4\xfa\x2a\x8d\x9c\x18\x1b\x72\xa2\x45\xf1\xcb\xcd\x6b\x50\x63\x5d\x0d\x1d\xfc\xf3\xef\x60\x8b\x22\x2a\x45\x85\x50\x7c\xb5\xff\x48\xf9\xec\x59\xf9\xd1\xbc\x42\x96\x9f\xad\xda\x84\xcf\x5f\x06\x95\x62\x8a\x3f\x36\xef\x8d\x81\xf8\x5f\x00\x00\x00\xff\xff\xf4\x0a\xda\x3f\xdf\x15\x00\x00")
 
 func aroOpenshiftIo_clustersYamlBytes() ([]byte, error) {
 	return bindataRead(
diff --git a/pkg/operator/deploy/staticresources/aro.openshift.io_clusters.yaml b/pkg/operator/deploy/staticresources/aro.openshift.io_clusters.yaml
index 96662c036..a87d24916 100644
--- a/pkg/operator/deploy/staticresources/aro.openshift.io_clusters.yaml
+++ b/pkg/operator/deploy/staticresources/aro.openshift.io_clusters.yaml
@@ -131,18 +131,8 @@ spec:
             description: ClusterStatus defines the observed state of Cluster
             properties:
               conditions:
-                description: Conditions is a set of Condition instances.
                 items:
-                  description: "Condition represents an observation of an object's
-                    state. Conditions are an extension mechanism intended to be used
-                    when the details of an observation are not a priori known or would
-                    not apply to all instances of a given Kind. \n Conditions should
-                    be added to explicitly convey properties that users and components
-                    care about rather than requiring those properties to be inferred
-                    from other observations. Once defined, the meaning of a Condition
-                    can not be changed arbitrarily - it becomes part of the API, and
-                    has the same backwards- and forwards-compatibility concerns of
-                    any other part of the API."
+                  description: OperatorCondition is just the standard condition fields.
                   properties:
                     lastTransitionTime:
                       format: date-time
@@ -150,24 +140,11 @@ spec:
                     message:
                       type: string
                     reason:
-                      description: ConditionReason is intended to be a one-word, CamelCase
-                        representation of the category of cause of the current status.
-                        It is intended to be used in concise output, such as one-line
-                        kubectl get output, and in summarizing occurrences of causes.
                       type: string
                     status:
                       type: string
                     type:
-                      description: "ConditionType is the type of the condition and
-                        is typically a CamelCased word or short phrase. \n Condition
-                        types should indicate state in the \"abnormal-true\" polarity.
-                        For example, if the condition indicates when a policy is invalid,
-                        the \"is valid\" case is probably the norm, so the condition
-                        should be called \"Invalid\"."
                       type: string
-                  required:
-                  - status
-                  - type
                   type: object
                 type: array
               operatorVersion:
diff --git a/pkg/operator/generate.go b/pkg/operator/generate.go
index 1d5f0db11..88aa4c350 100644
--- a/pkg/operator/generate.go
+++ b/pkg/operator/generate.go
@@ -7,7 +7,7 @@ package operator
 //go:generate go run ../../vendor/sigs.k8s.io/controller-tools/cmd/controller-gen object paths=./apis/aro.openshift.io/v1alpha1
 //go:generate sh -c "cd ../../../../.. && go run github.com/Azure/ARO-RP/vendor/k8s.io/code-generator/cmd/client-gen --clientset-name versioned --input-base github.com/Azure/ARO-RP/pkg/operator/apis --input aro.openshift.io/v1alpha1 --output-package github.com/Azure/ARO-RP/pkg/operator/clientset --go-header-file github.com/Azure/ARO-RP/hack/licenses/boilerplate.go.txt"
 //go:generate gofmt -s -w ./clientset
-//go:generate go run ../../vendor/golang.org/x/tools/cmd/goimports -w -local=github.com/Azure/ARO-RP ./clientset
+//go:generate go run ../../vendor/golang.org/x/tools/cmd/goimports -local=github.com/Azure/ARO-RP -e -w ./clientset ./apis
 
 // build the operator's CRD (based on the apis)
 // for master deployment
diff --git a/pkg/util/conditions/condition.go b/pkg/util/conditions/condition.go
new file mode 100644
index 000000000..e1e5531df
--- /dev/null
+++ b/pkg/util/conditions/condition.go
@@ -0,0 +1,116 @@
+package conditions
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+
+	operatorv1 "github.com/openshift/api/operator/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/util/clock"
+	"k8s.io/client-go/util/retry"
+
+	"github.com/Azure/ARO-RP/pkg/operator"
+	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
+	"github.com/Azure/ARO-RP/pkg/util/version"
+)
+
+// clock is used to set status condition timestamps.
+// This variable makes it easier to test conditions.
+var kubeclock clock.Clock = &clock.RealClock{}
+
+func SetCondition(ctx context.Context, arocli aroclient.Interface, cond *operatorv1.OperatorCondition, role string) error {
+	return retry.RetryOnConflict(retry.DefaultRetry, func() error {
+		if cond == nil {
+			return nil
+		}
+		cluster, err := arocli.AroV1alpha1().Clusters().Get(ctx, arov1alpha1.SingletonClusterName, metav1.GetOptions{})
+		if err != nil {
+			return err
+		}
+
+		var changed bool
+		cluster.Status.Conditions, changed = setCondition(cluster.Status.Conditions, *cond)
+
+		if cleanStaleConditions(cluster, role) {
+			changed = true
+		}
+
+		if !changed {
+			return nil
+		}
+
+		_, err = arocli.AroV1alpha1().Clusters().UpdateStatus(ctx, cluster, metav1.UpdateOptions{})
+		return err
+	})
+}
+
+func IsTrue(conditions []operatorv1.OperatorCondition, t string) bool {
+	return isCondition(conditions, t, operatorv1.ConditionTrue)
+}
+
+func IsFalse(conditions []operatorv1.OperatorCondition, t string) bool {
+	return isCondition(conditions, t, operatorv1.ConditionFalse)
+}
+
+func isCondition(conditions []operatorv1.OperatorCondition, t string, s operatorv1.ConditionStatus) bool {
+	for _, condition := range conditions {
+		if condition.Type == string(t) && condition.Status == s {
+			return true
+		}
+	}
+	return false
+}
+
+// cleanStaleConditions ensures that conditions no longer in use as defined by older operators
+// are always removed from the cluster.status.conditions
+func cleanStaleConditions(cluster *arov1alpha1.Cluster, role string) (changed bool) {
+	conditions := make([]operatorv1.OperatorCondition, 0, len(cluster.Status.Conditions))
+
+	// cleanup any old conditions
+	current := map[string]bool{}
+	for _, ct := range arov1alpha1.AllConditionTypes() {
+		current[ct] = true
+	}
+
+	for _, cond := range cluster.Status.Conditions {
+		if _, ok := current[cond.Type]; ok {
+			conditions = append(conditions, cond)
+		} else {
+			changed = true
+		}
+	}
+
+	cluster.Status.Conditions = conditions
+
+	if role == operator.RoleMaster && cluster.Status.OperatorVersion != version.GitCommit {
+		cluster.Status.OperatorVersion = version.GitCommit
+		changed = true
+	}
+
+	return
+}
+
+// SetCondition adds (or updates) the set of conditions with the given
+// condition. It returns a boolean value indicating whether the set condition
+// is new or was a change to the existing condition with the same type.
+func setCondition(conditions []operatorv1.OperatorCondition, newCond operatorv1.OperatorCondition) ([]operatorv1.OperatorCondition, bool) {
+	newCond.LastTransitionTime = metav1.Time{Time: kubeclock.Now()}
+
+	for i, condition := range conditions {
+		if condition.Type == newCond.Type {
+			if condition.Status == newCond.Status {
+				newCond.LastTransitionTime = condition.LastTransitionTime
+			}
+			changed := condition.Status != newCond.Status ||
+				condition.Reason != newCond.Reason ||
+				condition.Message != newCond.Message
+			(conditions)[i] = newCond
+			return conditions, changed
+		}
+	}
+	conditions = append(conditions, newCond)
+	return conditions, true
+}
diff --git a/vendor/github.com/operator-framework/operator-sdk/LICENSE b/vendor/github.com/operator-framework/operator-sdk/LICENSE
deleted file mode 100644
index 261eeb9e9..000000000
--- a/vendor/github.com/operator-framework/operator-sdk/LICENSE
+++ /dev/null
@@ -1,201 +0,0 @@
-                                 Apache License
-                           Version 2.0, January 2004
-                        http://www.apache.org/licenses/
-
-   TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
-
-   1. Definitions.
-
-      "License" shall mean the terms and conditions for use, reproduction,
-      and distribution as defined by Sections 1 through 9 of this document.
-
-      "Licensor" shall mean the copyright owner or entity authorized by
-      the copyright owner that is granting the License.
-
-      "Legal Entity" shall mean the union of the acting entity and all
-      other entities that control, are controlled by, or are under common
-      control with that entity. For the purposes of this definition,
-      "control" means (i) the power, direct or indirect, to cause the
-      direction or management of such entity, whether by contract or
-      otherwise, or (ii) ownership of fifty percent (50%) or more of the
-      outstanding shares, or (iii) beneficial ownership of such entity.
-
-      "You" (or "Your") shall mean an individual or Legal Entity
-      exercising permissions granted by this License.
-
-      "Source" form shall mean the preferred form for making modifications,
-      including but not limited to software source code, documentation
-      source, and configuration files.
-
-      "Object" form shall mean any form resulting from mechanical
-      transformation or translation of a Source form, including but
-      not limited to compiled object code, generated documentation,
-      and conversions to other media types.
-
-      "Work" shall mean the work of authorship, whether in Source or
-      Object form, made available under the License, as indicated by a
-      copyright notice that is included in or attached to the work
-      (an example is provided in the Appendix below).
-
-      "Derivative Works" shall mean any work, whether in Source or Object
-      form, that is based on (or derived from) the Work and for which the
-      editorial revisions, annotations, elaborations, or other modifications
-      represent, as a whole, an original work of authorship. For the purposes
-      of this License, Derivative Works shall not include works that remain
-      separable from, or merely link (or bind by name) to the interfaces of,
-      the Work and Derivative Works thereof.
-
-      "Contribution" shall mean any work of authorship, including
-      the original version of the Work and any modifications or additions
-      to that Work or Derivative Works thereof, that is intentionally
-      submitted to Licensor for inclusion in the Work by the copyright owner
-      or by an individual or Legal Entity authorized to submit on behalf of
-      the copyright owner. For the purposes of this definition, "submitted"
-      means any form of electronic, verbal, or written communication sent
-      to the Licensor or its representatives, including but not limited to
-      communication on electronic mailing lists, source code control systems,
-      and issue tracking systems that are managed by, or on behalf of, the
-      Licensor for the purpose of discussing and improving the Work, but
-      excluding communication that is conspicuously marked or otherwise
-      designated in writing by the copyright owner as "Not a Contribution."
-
-      "Contributor" shall mean Licensor and any individual or Legal Entity
-      on behalf of whom a Contribution has been received by Licensor and
-      subsequently incorporated within the Work.
-
-   2. Grant of Copyright License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      copyright license to reproduce, prepare Derivative Works of,
-      publicly display, publicly perform, sublicense, and distribute the
-      Work and such Derivative Works in Source or Object form.
-
-   3. Grant of Patent License. Subject to the terms and conditions of
-      this License, each Contributor hereby grants to You a perpetual,
-      worldwide, non-exclusive, no-charge, royalty-free, irrevocable
-      (except as stated in this section) patent license to make, have made,
-      use, offer to sell, sell, import, and otherwise transfer the Work,
-      where such license applies only to those patent claims licensable
-      by such Contributor that are necessarily infringed by their
-      Contribution(s) alone or by combination of their Contribution(s)
-      with the Work to which such Contribution(s) was submitted. If You
-      institute patent litigation against any entity (including a
-      cross-claim or counterclaim in a lawsuit) alleging that the Work
-      or a Contribution incorporated within the Work constitutes direct
-      or contributory patent infringement, then any patent licenses
-      granted to You under this License for that Work shall terminate
-      as of the date such litigation is filed.
-
-   4. Redistribution. You may reproduce and distribute copies of the
-      Work or Derivative Works thereof in any medium, with or without
-      modifications, and in Source or Object form, provided that You
-      meet the following conditions:
-
-      (a) You must give any other recipients of the Work or
-          Derivative Works a copy of this License; and
-
-      (b) You must cause any modified files to carry prominent notices
-          stating that You changed the files; and
-
-      (c) You must retain, in the Source form of any Derivative Works
-          that You distribute, all copyright, patent, trademark, and
-          attribution notices from the Source form of the Work,
-          excluding those notices that do not pertain to any part of
-          the Derivative Works; and
-
-      (d) If the Work includes a "NOTICE" text file as part of its
-          distribution, then any Derivative Works that You distribute must
-          include a readable copy of the attribution notices contained
-          within such NOTICE file, excluding those notices that do not
-          pertain to any part of the Derivative Works, in at least one
-          of the following places: within a NOTICE text file distributed
-          as part of the Derivative Works; within the Source form or
-          documentation, if provided along with the Derivative Works; or,
-          within a display generated by the Derivative Works, if and
-          wherever such third-party notices normally appear. The contents
-          of the NOTICE file are for informational purposes only and
-          do not modify the License. You may add Your own attribution
-          notices within Derivative Works that You distribute, alongside
-          or as an addendum to the NOTICE text from the Work, provided
-          that such additional attribution notices cannot be construed
-          as modifying the License.
-
-      You may add Your own copyright statement to Your modifications and
-      may provide additional or different license terms and conditions
-      for use, reproduction, or distribution of Your modifications, or
-      for any such Derivative Works as a whole, provided Your use,
-      reproduction, and distribution of the Work otherwise complies with
-      the conditions stated in this License.
-
-   5. Submission of Contributions. Unless You explicitly state otherwise,
-      any Contribution intentionally submitted for inclusion in the Work
-      by You to the Licensor shall be under the terms and conditions of
-      this License, without any additional terms or conditions.
-      Notwithstanding the above, nothing herein shall supersede or modify
-      the terms of any separate license agreement you may have executed
-      with Licensor regarding such Contributions.
-
-   6. Trademarks. This License does not grant permission to use the trade
-      names, trademarks, service marks, or product names of the Licensor,
-      except as required for reasonable and customary use in describing the
-      origin of the Work and reproducing the content of the NOTICE file.
-
-   7. Disclaimer of Warranty. Unless required by applicable law or
-      agreed to in writing, Licensor provides the Work (and each
-      Contributor provides its Contributions) on an "AS IS" BASIS,
-      WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
-      implied, including, without limitation, any warranties or conditions
-      of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
-      PARTICULAR PURPOSE. You are solely responsible for determining the
-      appropriateness of using or redistributing the Work and assume any
-      risks associated with Your exercise of permissions under this License.
-
-   8. Limitation of Liability. In no event and under no legal theory,
-      whether in tort (including negligence), contract, or otherwise,
-      unless required by applicable law (such as deliberate and grossly
-      negligent acts) or agreed to in writing, shall any Contributor be
-      liable to You for damages, including any direct, indirect, special,
-      incidental, or consequential damages of any character arising as a
-      result of this License or out of the use or inability to use the
-      Work (including but not limited to damages for loss of goodwill,
-      work stoppage, computer failure or malfunction, or any and all
-      other commercial damages or losses), even if such Contributor
-      has been advised of the possibility of such damages.
-
-   9. Accepting Warranty or Additional Liability. While redistributing
-      the Work or Derivative Works thereof, You may choose to offer,
-      and charge a fee for, acceptance of support, warranty, indemnity,
-      or other liability obligations and/or rights consistent with this
-      License. However, in accepting such obligations, You may act only
-      on Your own behalf and on Your sole responsibility, not on behalf
-      of any other Contributor, and only if You agree to indemnify,
-      defend, and hold each Contributor harmless for any liability
-      incurred by, or claims asserted against, such Contributor by reason
-      of your accepting any such warranty or additional liability.
-
-   END OF TERMS AND CONDITIONS
-
-   APPENDIX: How to apply the Apache License to your work.
-
-      To apply the Apache License to your work, attach the following
-      boilerplate notice, with the fields enclosed by brackets "[]"
-      replaced with your own identifying information. (Don't include
-      the brackets!)  The text should be enclosed in the appropriate
-      comment syntax for the file format. We also recommend that a
-      file or class name and description of purpose be included on the
-      same "printed page" as the copyright notice for easier
-      identification within third-party archives.
-
-   Copyright [yyyy] [name of copyright owner]
-
-   Licensed under the Apache License, Version 2.0 (the "License");
-   you may not use this file except in compliance with the License.
-   You may obtain a copy of the License at
-
-       http://www.apache.org/licenses/LICENSE-2.0
-
-   Unless required by applicable law or agreed to in writing, software
-   distributed under the License is distributed on an "AS IS" BASIS,
-   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-   See the License for the specific language governing permissions and
-   limitations under the License.
diff --git a/vendor/github.com/operator-framework/operator-sdk/pkg/status/conditions.go b/vendor/github.com/operator-framework/operator-sdk/pkg/status/conditions.go
deleted file mode 100644
index 070cf863a..000000000
--- a/vendor/github.com/operator-framework/operator-sdk/pkg/status/conditions.go
+++ /dev/null
@@ -1,189 +0,0 @@
-// Copyright 2020 The Operator-SDK Authors
-//
-// Licensed under the Apache License, Version 2.0 (the "License");
-// you may not use this file except in compliance with the License.
-// You may obtain a copy of the License at
-//
-//     http://www.apache.org/licenses/LICENSE-2.0
-//
-// Unless required by applicable law or agreed to in writing, software
-// distributed under the License is distributed on an "AS IS" BASIS,
-// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-// See the License for the specific language governing permissions and
-// limitations under the License.
-
-package status
-
-import (
-	"encoding/json"
-	"sort"
-
-	corev1 "k8s.io/api/core/v1"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	kubeclock "k8s.io/apimachinery/pkg/util/clock"
-)
-
-// clock is used to set status condition timestamps.
-// This variable makes it easier to test conditions.
-var clock kubeclock.Clock = &kubeclock.RealClock{}
-
-// ConditionType is the type of the condition and is typically a CamelCased
-// word or short phrase.
-//
-// Condition types should indicate state in the "abnormal-true" polarity. For
-// example, if the condition indicates when a policy is invalid, the "is valid"
-// case is probably the norm, so the condition should be called "Invalid".
-type ConditionType string
-
-// ConditionReason is intended to be a one-word, CamelCase representation of
-// the category of cause of the current status. It is intended to be used in
-// concise output, such as one-line kubectl get output, and in summarizing
-// occurrences of causes.
-type ConditionReason string
-
-// Condition represents an observation of an object's state. Conditions are an
-// extension mechanism intended to be used when the details of an observation
-// are not a priori known or would not apply to all instances of a given Kind.
-//
-// Conditions should be added to explicitly convey properties that users and
-// components care about rather than requiring those properties to be inferred
-// from other observations. Once defined, the meaning of a Condition can not be
-// changed arbitrarily - it becomes part of the API, and has the same
-// backwards- and forwards-compatibility concerns of any other part of the API.
-type Condition struct {
-	Type               ConditionType          `json:"type"`
-	Status             corev1.ConditionStatus `json:"status"`
-	Reason             ConditionReason        `json:"reason,omitempty"`
-	Message            string                 `json:"message,omitempty"`
-	LastTransitionTime metav1.Time            `json:"lastTransitionTime,omitempty"`
-}
-
-// IsTrue Condition whether the condition status is "True".
-func (c Condition) IsTrue() bool {
-	return c.Status == corev1.ConditionTrue
-}
-
-// IsFalse returns whether the condition status is "False".
-func (c Condition) IsFalse() bool {
-	return c.Status == corev1.ConditionFalse
-}
-
-// IsUnknown returns whether the condition status is "Unknown".
-func (c Condition) IsUnknown() bool {
-	return c.Status == corev1.ConditionUnknown
-}
-
-// DeepCopyInto copies in into out.
-func (c *Condition) DeepCopyInto(cpy *Condition) {
-	*cpy = *c
-}
-
-// Conditions is a set of Condition instances.
-type Conditions []Condition
-
-// NewConditions initializes a set of conditions with the given list of
-// conditions.
-func NewConditions(conds ...Condition) Conditions {
-	conditions := Conditions{}
-	for _, c := range conds {
-		conditions.SetCondition(c)
-	}
-	return conditions
-}
-
-// IsTrueFor searches the set of conditions for a condition with the given
-// ConditionType. If found, it returns `condition.IsTrue()`. If not found,
-// it returns false.
-func (conditions Conditions) IsTrueFor(t ConditionType) bool {
-	for _, condition := range conditions {
-		if condition.Type == t {
-			return condition.IsTrue()
-		}
-	}
-	return false
-}
-
-// IsFalseFor searches the set of conditions for a condition with the given
-// ConditionType. If found, it returns `condition.IsFalse()`. If not found,
-// it returns false.
-func (conditions Conditions) IsFalseFor(t ConditionType) bool {
-	for _, condition := range conditions {
-		if condition.Type == t {
-			return condition.IsFalse()
-		}
-	}
-	return false
-}
-
-// IsUnknownFor searches the set of conditions for a condition with the given
-// ConditionType. If found, it returns `condition.IsUnknown()`. If not found,
-// it returns true.
-func (conditions Conditions) IsUnknownFor(t ConditionType) bool {
-	for _, condition := range conditions {
-		if condition.Type == t {
-			return condition.IsUnknown()
-		}
-	}
-	return true
-}
-
-// SetCondition adds (or updates) the set of conditions with the given
-// condition. It returns a boolean value indicating whether the set condition
-// is new or was a change to the existing condition with the same type.
-func (conditions *Conditions) SetCondition(newCond Condition) bool {
-	newCond.LastTransitionTime = metav1.Time{Time: clock.Now()}
-
-	for i, condition := range *conditions {
-		if condition.Type == newCond.Type {
-			if condition.Status == newCond.Status {
-				newCond.LastTransitionTime = condition.LastTransitionTime
-			}
-			changed := condition.Status != newCond.Status ||
-				condition.Reason != newCond.Reason ||
-				condition.Message != newCond.Message
-			(*conditions)[i] = newCond
-			return changed
-		}
-	}
-	*conditions = append(*conditions, newCond)
-	return true
-}
-
-// GetCondition searches the set of conditions for the condition with the given
-// ConditionType and returns it. If the matching condition is not found,
-// GetCondition returns nil.
-func (conditions Conditions) GetCondition(t ConditionType) *Condition {
-	for _, condition := range conditions {
-		if condition.Type == t {
-			return &condition
-		}
-	}
-	return nil
-}
-
-// RemoveCondition removes the condition with the given ConditionType from
-// the conditions set. If no condition with that type is found, RemoveCondition
-// returns without performing any action. If the passed condition type is not
-// found in the set of conditions, RemoveCondition returns false.
-func (conditions *Conditions) RemoveCondition(t ConditionType) bool {
-	if conditions == nil {
-		return false
-	}
-	for i, condition := range *conditions {
-		if condition.Type == t {
-			*conditions = append((*conditions)[:i], (*conditions)[i+1:]...)
-			return true
-		}
-	}
-	return false
-}
-
-// MarshalJSON marshals the set of conditions as a JSON array, sorted by
-// condition type.
-func (conditions Conditions) MarshalJSON() ([]byte, error) {
-	conds := []Condition(conditions)
-	sort.Slice(conds, func(a, b int) bool {
-		return conds[a].Type < conds[b].Type
-	})
-	return json.Marshal(conds)
-}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index 786645037..e5cc8720e 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -938,9 +938,6 @@ github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/f
 github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/scheme
 github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/typed/machineconfiguration.openshift.io/v1
 github.com/openshift/machine-config-operator/pkg/generated/clientset/versioned/typed/machineconfiguration.openshift.io/v1/fake
-# github.com/operator-framework/operator-sdk v1.7.2 => github.com/operator-framework/operator-sdk v0.19.4
-## explicit
-github.com/operator-framework/operator-sdk/pkg/status
 # github.com/ovirt/go-ovirt v4.3.4+incompatible => github.com/ovirt/go-ovirt v0.0.0-20210112072624-e4d3b104de71
 github.com/ovirt/go-ovirt
 # github.com/pascaldekloe/name v0.0.0-20180628100202-0fd16699aae1
@@ -958,6 +955,7 @@ github.com/phayes/checkstyle
 ## explicit
 github.com/pires/go-proxyproto
 # github.com/pkg/errors v0.9.1
+## explicit
 github.com/pkg/errors
 # github.com/pmezard/go-difflib v1.0.0
 github.com/pmezard/go-difflib/difflib


Test patch:
diff --git a/pkg/operator/controllers/banner/banner_controller_test.go b/pkg/operator/controllers/banner/banner_controller_test.go
index 79b1786c9..374768320 100644
--- a/pkg/operator/controllers/banner/banner_controller_test.go
+++ b/pkg/operator/controllers/banner/banner_controller_test.go
@@ -5,6 +5,7 @@ package banner
 
 import (
 	"context"
+	"fmt"
 	"strings"
 	"testing"
 
@@ -27,14 +28,14 @@ func TestBannerReconcile(t *testing.T) {
 		bannerSetting   string
 		expectBanner    bool
 		expectedMessage string
-		wantErr         string
+		expectedErr     error
 		featureFlag     bool
 	}{
 		{
 			name:          "Wrong banner setting",
 			bannerSetting: "WRONG",
 			expectBanner:  false,
-			wantErr:       "wrong banner setting 'WRONG'",
+			expectedErr:   fmt.Errorf("wrong banner setting 'WRONG'"),
 			featureFlag:   true,
 		},
 		{
@@ -150,9 +151,14 @@ func TestBannerReconcile(t *testing.T) {
 			// function under test
 			_, err := r.Reconcile(context.Background(), ctrl.Request{})
 
-			if err != nil && err.Error() != tt.wantErr ||
-				err == nil && tt.wantErr != "" {
-				t.Error(err)
+			if err != nil {
+				if tt.expectedErr == nil {
+					t.Error(err)
+				}
+				if !strings.EqualFold(err.Error(), tt.expectedErr.Error()) {
+					t.Error(err)
+				}
+				return
 			}
 			resultBanner, err := r.consolecli.ConsoleV1().ConsoleNotifications().Get(context.Background(), BannerName, metav1.GetOptions{})
 			if tt.expectBanner {
diff --git a/pkg/operator/controllers/machine/machine_controller_test.go b/pkg/operator/controllers/machine/machine_controller_test.go
index fa67406c4..2ab508cda 100644
--- a/pkg/operator/controllers/machine/machine_controller_test.go
+++ b/pkg/operator/controllers/machine/machine_controller_test.go
@@ -10,11 +10,10 @@ import (
 	"testing"
 
 	"github.com/Azure/go-autorest/autorest/to"
+	operatorv1 "github.com/openshift/api/operator/v1"
 	machinev1beta1 "github.com/openshift/machine-api-operator/pkg/apis/machine/v1beta1"
 	maofake "github.com/openshift/machine-api-operator/pkg/generated/clientset/versioned/fake"
-	"github.com/operator-framework/operator-sdk/pkg/status"
 	"github.com/sirupsen/logrus"
-	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/runtime"
 	ctrl "sigs.k8s.io/controller-runtime"
@@ -69,7 +68,7 @@ func TestMachineReconciler(t *testing.T) {
 
 	baseCluster := arov1alpha1.Cluster{
 		ObjectMeta: metav1.ObjectMeta{Name: "cluster"},
-		Status:     arov1alpha1.ClusterStatus{Conditions: []status.Condition{}},
+		Status:     arov1alpha1.ClusterStatus{Conditions: []operatorv1.OperatorCondition{}},
 	}
 
 	tests := []struct {
@@ -77,14 +76,14 @@ func TestMachineReconciler(t *testing.T) {
 		request        ctrl.Request
 		maocli         *maofake.Clientset
 		arocli         *arofake.Clientset
-		wantConditions []status.Condition
+		wantConditions []operatorv1.OperatorCondition
 	}{
 		{
 			name:   "valid",
 			maocli: newFakeMao1("", "", "", ""),
-			wantConditions: []status.Condition{{
+			wantConditions: []operatorv1.OperatorCondition{{
 				Type:    arov1alpha1.MachineValid,
-				Status:  corev1.ConditionTrue,
+				Status:  operatorv1.ConditionTrue,
 				Message: "All machines valid",
 				Reason:  "CheckDone",
 			}},
@@ -92,9 +91,9 @@ func TestMachineReconciler(t *testing.T) {
 		{
 			name:   "wrong vm size",
 			maocli: newFakeMao1("", "", "Standard_D4s_v9", ""),
-			wantConditions: []status.Condition{{
+			wantConditions: []operatorv1.OperatorCondition{{
 				Type:    arov1alpha1.MachineValid,
-				Status:  corev1.ConditionFalse,
+				Status:  operatorv1.ConditionFalse,
 				Message: "machine foo-hx8z7-worker-2: invalid VM size 'Standard_D4s_v9'",
 				Reason:  "CheckFailed",
 			}},
@@ -102,9 +101,9 @@ func TestMachineReconciler(t *testing.T) {
 		{
 			name:   "wrong disk size",
 			maocli: newFakeMao1("64", "", "", ""),
-			wantConditions: []status.Condition{{
+			wantConditions: []operatorv1.OperatorCondition{{
 				Type:    arov1alpha1.MachineValid,
-				Status:  corev1.ConditionFalse,
+				Status:  operatorv1.ConditionFalse,
 				Message: "machine foo-hx8z7-worker-0: invalid disk size '64'",
 				Reason:  "CheckFailed",
 			}},
@@ -112,9 +111,9 @@ func TestMachineReconciler(t *testing.T) {
 		{
 			name:   "wrong image publisher",
 			maocli: newFakeMao1("", "bananas", "", ""),
-			wantConditions: []status.Condition{{
+			wantConditions: []operatorv1.OperatorCondition{{
 				Type:    arov1alpha1.MachineValid,
-				Status:  corev1.ConditionFalse,
+				Status:  operatorv1.ConditionFalse,
 				Message: "machine foo-hx8z7-worker-1: invalid image '{bananas aro4   }'",
 				Reason:  "CheckFailed",
 			}},
@@ -122,9 +121,9 @@ func TestMachineReconciler(t *testing.T) {
 		{
 			name:   "wrong vm size on master",
 			maocli: newFakeMao1("", "", "", "Standard_D4s_v9"),
-			wantConditions: []status.Condition{{
+			wantConditions: []operatorv1.OperatorCondition{{
 				Type:    arov1alpha1.MachineValid,
-				Status:  corev1.ConditionFalse,
+				Status:  operatorv1.ConditionFalse,
 				Message: "machine foo-hx8z7-master-2: invalid VM size 'Standard_D4s_v9'",
 				Reason:  "CheckFailed",
 			}},
@@ -132,9 +131,9 @@ func TestMachineReconciler(t *testing.T) {
 		{
 			name:   "invalid master machine count",
 			maocli: newFakeMao2(),
-			wantConditions: []status.Condition{{
+			wantConditions: []operatorv1.OperatorCondition{{
 				Type:    arov1alpha1.MachineValid,
-				Status:  corev1.ConditionFalse,
+				Status:  operatorv1.ConditionFalse,
 				Message: "invalid number of master machines 2, expected 3",
 				Reason:  "CheckFailed",
 			}},
@@ -142,9 +141,9 @@ func TestMachineReconciler(t *testing.T) {
 		{
 			name:   "invalid worker machine count",
 			maocli: newFakeMao3(),
-			wantConditions: []status.Condition{{
+			wantConditions: []operatorv1.OperatorCondition{{
 				Type:    arov1alpha1.MachineValid,
-				Status:  corev1.ConditionFalse,
+				Status:  operatorv1.ConditionFalse,
 				Message: "invalid number of worker machines 2, expected 3",
 				Reason:  "CheckFailed",
 			}},
diff --git a/pkg/operator/controllers/pullsecret/pullsecret_controller_test.go b/pkg/operator/controllers/pullsecret/pullsecret_controller_test.go
index 249361539..d0a521f35 100644
--- a/pkg/operator/controllers/pullsecret/pullsecret_controller_test.go
+++ b/pkg/operator/controllers/pullsecret/pullsecret_controller_test.go
@@ -9,7 +9,6 @@ import (
 	"reflect"
 	"testing"
 
-	"github.com/operator-framework/operator-sdk/pkg/status"
 	"github.com/sirupsen/logrus"
 	corev1 "k8s.io/api/core/v1"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -59,17 +58,16 @@ func TestPullSecretReconciler(t *testing.T) {
 	}
 
 	tests := []struct {
-		name           string
-		request        ctrl.Request
-		fakecli        *fake.Clientset
-		arocli         *arofake.Clientset
-		wantConditions []status.Condition
-		wantKeys       []string
-		wantErr        bool
-		want           string
-		wantCreated    bool
-		wantDeleted    bool
-		wantUpdated    bool
+		name        string
+		request     ctrl.Request
+		fakecli     *fake.Clientset
+		arocli      *arofake.Clientset
+		wantKeys    []string
+		wantErr     bool
+		want        string
+		wantCreated bool
+		wantDeleted bool
+		wantUpdated bool
 	}{
 		{
 			name: "deleted pull secret",
diff --git a/pkg/util/conditions/condition_test.go b/pkg/util/conditions/condition_test.go
new file mode 100644
index 000000000..bcfb4cee3
--- /dev/null
+++ b/pkg/util/conditions/condition_test.go
@@ -0,0 +1,305 @@
+package conditions
+
+// Copyright (c) Microsoft Corporation.
+// Licensed under the Apache License 2.0.
+
+import (
+	"context"
+	"reflect"
+	"testing"
+	"time"
+
+	operatorv1 "github.com/openshift/api/operator/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/util/clock"
+
+	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
+	aroclient "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned"
+	arofake "github.com/Azure/ARO-RP/pkg/operator/clientset/versioned/fake"
+	"github.com/Azure/ARO-RP/pkg/util/cmp"
+)
+
+func TestSetCondition(t *testing.T) {
+	ctx := context.Background()
+	role := "master"
+	objectName := "cluster"
+	version := "unknown"
+
+	kubeclock = &clock.FakeClock{}
+	var transitionTime metav1.Time = metav1.Time{Time: kubeclock.Now()}
+
+	for _, tt := range []struct {
+		name      string
+		aroclient aroclient.Interface
+		objects   []runtime.Object
+		input     *operatorv1.OperatorCondition
+
+		expected arov1alpha1.ClusterStatus
+		wantErr  error
+	}{
+		{
+			name: "no condition provided",
+			objects: []runtime.Object{&arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: objectName,
+				},
+			}},
+			expected: arov1alpha1.ClusterStatus{},
+		},
+		{
+			name: "new condition provided",
+			objects: []runtime.Object{&arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: objectName,
+				},
+				Status: arov1alpha1.ClusterStatus{
+					Conditions:      []operatorv1.OperatorCondition{},
+					OperatorVersion: version,
+				},
+			}},
+			input: &operatorv1.OperatorCondition{
+				Type:   arov1alpha1.InternetReachableFromMaster,
+				Status: operatorv1.ConditionFalse,
+			},
+			expected: arov1alpha1.ClusterStatus{
+				Conditions: []operatorv1.OperatorCondition{
+					{
+						Type:   arov1alpha1.InternetReachableFromMaster,
+						Status: operatorv1.ConditionFalse,
+					},
+				},
+				OperatorVersion: version,
+			},
+		},
+		{
+			name: "condition provided without status change - only update operator version",
+			objects: []runtime.Object{&arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: objectName,
+				},
+				Status: arov1alpha1.ClusterStatus{
+					Conditions: []operatorv1.OperatorCondition{
+						{
+							Type:   arov1alpha1.InternetReachableFromMaster,
+							Status: operatorv1.ConditionFalse,
+						},
+					},
+					OperatorVersion: "?",
+				},
+			}},
+			input: &operatorv1.OperatorCondition{
+				Type:   arov1alpha1.InternetReachableFromMaster,
+				Status: operatorv1.ConditionFalse,
+			},
+			expected: arov1alpha1.ClusterStatus{
+				Conditions: []operatorv1.OperatorCondition{
+					{
+						Type:   arov1alpha1.InternetReachableFromMaster,
+						Status: operatorv1.ConditionFalse,
+					},
+				},
+				OperatorVersion: version,
+			},
+		},
+		{
+			name: "condition provided without status change - no update",
+			objects: []runtime.Object{&arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: objectName,
+				},
+				Status: arov1alpha1.ClusterStatus{
+					Conditions: []operatorv1.OperatorCondition{
+						{
+							Type:               arov1alpha1.InternetReachableFromMaster,
+							Status:             operatorv1.ConditionFalse,
+							LastTransitionTime: metav1.Time{Time: time.Date(1970, 0, 0, 0, 0, 0, 0, time.UTC)},
+						},
+					},
+					OperatorVersion: version,
+				},
+			}},
+			input: &operatorv1.OperatorCondition{
+				Type:               arov1alpha1.InternetReachableFromMaster,
+				Status:             operatorv1.ConditionFalse,
+				LastTransitionTime: metav1.Time{Time: time.Date(2021, 0, 0, 0, 0, 0, 0, time.UTC)},
+			},
+			expected: arov1alpha1.ClusterStatus{
+				Conditions: []operatorv1.OperatorCondition{
+					{
+						Type:               arov1alpha1.InternetReachableFromMaster,
+						Status:             operatorv1.ConditionFalse,
+						LastTransitionTime: metav1.Time{Time: time.Date(1970, 0, 0, 0, 0, 0, 0, time.UTC)},
+					},
+				},
+				OperatorVersion: version,
+			},
+		},
+		{
+			name: "update one of the existing conditions",
+			objects: []runtime.Object{&arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: objectName,
+				},
+				Status: arov1alpha1.ClusterStatus{
+					Conditions: []operatorv1.OperatorCondition{
+						{
+							Type:   arov1alpha1.InternetReachableFromMaster,
+							Status: operatorv1.ConditionFalse,
+						},
+						{
+							Type:   arov1alpha1.MachineValid,
+							Status: operatorv1.ConditionFalse,
+						},
+					},
+					OperatorVersion: "?",
+				},
+			}},
+			input: &operatorv1.OperatorCondition{
+				Type:   arov1alpha1.InternetReachableFromMaster,
+				Status: operatorv1.ConditionTrue,
+			},
+			expected: arov1alpha1.ClusterStatus{
+				Conditions: []operatorv1.OperatorCondition{
+					{
+						Type:               arov1alpha1.InternetReachableFromMaster,
+						Status:             operatorv1.ConditionTrue,
+						LastTransitionTime: transitionTime,
+					},
+					{
+						Type:   arov1alpha1.MachineValid,
+						Status: operatorv1.ConditionFalse,
+					},
+				},
+				OperatorVersion: version,
+			},
+		},
+		{
+			name: "cleanup stale conditions",
+			objects: []runtime.Object{&arov1alpha1.Cluster{
+				ObjectMeta: metav1.ObjectMeta{
+					Name: objectName,
+				},
+				Status: arov1alpha1.ClusterStatus{
+					Conditions: []operatorv1.OperatorCondition{
+						{
+							Type:   "staleType",
+							Status: operatorv1.ConditionTrue,
+						},
+					},
+					OperatorVersion: version,
+				},
+			}},
+			input: &operatorv1.OperatorCondition{
+				Type:   arov1alpha1.InternetReachableFromMaster,
+				Status: operatorv1.ConditionTrue,
+			},
+			expected: arov1alpha1.ClusterStatus{
+				Conditions: []operatorv1.OperatorCondition{
+					{
+						Type:               arov1alpha1.InternetReachableFromMaster,
+						Status:             operatorv1.ConditionTrue,
+						LastTransitionTime: transitionTime,
+					},
+				},
+				OperatorVersion: version,
+			},
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			client := arofake.NewSimpleClientset(tt.objects...)
+
+			err := SetCondition(ctx, client, tt.input, role)
+			if err != nil && tt.wantErr != nil {
+				t.Fatal(err.Error())
+			}
+
+			result, err := client.AroV1alpha1().Clusters().Get(ctx, objectName, metav1.GetOptions{})
+			if err != nil {
+				t.Fatal(err.Error())
+			}
+
+			if !reflect.DeepEqual(result.Status, tt.expected) {
+				t.Fatal(cmp.Diff(result.Status, tt.expected))
+			}
+		})
+	}
+}
+
+func TestIsConditions(t *testing.T) {
+	for _, tt := range []struct {
+		name       string
+		conditions []operatorv1.OperatorCondition
+		t          string
+		f          func([]operatorv1.OperatorCondition, string) bool
+		expect     bool
+	}{
+		{
+			name: "IsTrue - non-existing",
+			conditions: []operatorv1.OperatorCondition{
+				{
+					Type:   arov1alpha1.InternetReachableFromWorker,
+					Status: operatorv1.ConditionTrue,
+				},
+			},
+			t:      arov1alpha1.InternetReachableFromMaster,
+			f:      IsTrue,
+			expect: false,
+		},
+		{
+			name: "IsTrue - true",
+			conditions: []operatorv1.OperatorCondition{
+				{
+					Type:   arov1alpha1.InternetReachableFromMaster,
+					Status: operatorv1.ConditionTrue,
+				},
+			},
+			t:      arov1alpha1.InternetReachableFromMaster,
+			f:      IsTrue,
+			expect: true,
+		},
+		{
+			name: "IsTrue - false",
+			conditions: []operatorv1.OperatorCondition{
+				{
+					Type:   arov1alpha1.InternetReachableFromMaster,
+					Status: operatorv1.ConditionFalse,
+				},
+			},
+			t:      arov1alpha1.InternetReachableFromMaster,
+			f:      IsTrue,
+			expect: false,
+		},
+		{
+			name: "IsFalse - true",
+			conditions: []operatorv1.OperatorCondition{
+				{
+					Type:   arov1alpha1.InternetReachableFromMaster,
+					Status: operatorv1.ConditionFalse,
+				},
+			},
+			t:      arov1alpha1.InternetReachableFromMaster,
+			f:      IsFalse,
+			expect: true,
+		},
+		{
+			name: "IsFalse - false",
+			conditions: []operatorv1.OperatorCondition{
+				{
+					Type:   arov1alpha1.InternetReachableFromMaster,
+					Status: operatorv1.ConditionTrue,
+				},
+			},
+			t:      arov1alpha1.InternetReachableFromMaster,
+			f:      IsFalse,
+			expect: false,
+		},
+	} {
+		t.Run(tt.name, func(t *testing.T) {
+			result := tt.f(tt.conditions, tt.t)
+			if result != tt.expect {
+				t.Fatalf("expected %t, got %t", tt.expect, result)
+			}
+		})
+	}
+}
diff --git a/test/e2e/operator.go b/test/e2e/operator.go
index f636bf396..f6411a0bd 100644
--- a/test/e2e/operator.go
+++ b/test/e2e/operator.go
@@ -23,6 +23,7 @@ import (
 
 	arov1alpha1 "github.com/Azure/ARO-RP/pkg/operator/apis/aro.openshift.io/v1alpha1"
 	"github.com/Azure/ARO-RP/pkg/operator/controllers/monitoring"
+	"github.com/Azure/ARO-RP/pkg/util/conditions"
 	"github.com/Azure/ARO-RP/pkg/util/ready"
 )
 
@@ -93,13 +94,13 @@ var _ = Describe("ARO Operator - Internet checking", func() {
 	Specify("the InternetReachable default list should all be reachable", func() {
 		co, err := clients.AROClusters.AroV1alpha1().Clusters().Get(context.Background(), "cluster", metav1.GetOptions{})
 		Expect(err).NotTo(HaveOccurred())
-		Expect(co.Status.Conditions.IsTrueFor(arov1alpha1.InternetReachableFromMaster)).To(BeTrue())
+		Expect(conditions.IsTrue(co.Status.Conditions, arov1alpha1.InternetReachableFromMaster)).To(BeTrue())
 	})
 
 	Specify("the InternetReachable default list should all be reachable from worker", func() {
 		co, err := clients.AROClusters.AroV1alpha1().Clusters().Get(context.Background(), "cluster", metav1.GetOptions{})
 		Expect(err).NotTo(HaveOccurred())
-		Expect(co.Status.Conditions.IsTrueFor(arov1alpha1.InternetReachableFromWorker)).To(BeTrue())
+		Expect(conditions.IsTrue(co.Status.Conditions, arov1alpha1.InternetReachableFromWorker)).To(BeTrue())
 	})
 
 	Specify("custom invalid site shows not InternetReachable", func() {
@@ -124,8 +125,8 @@ var _ = Describe("ARO Operator - Internet checking", func() {
 			}
 
 			log.Debugf("ClusterStatus.Conditions %s", co.Status.Conditions)
-			return co.Status.Conditions.IsFalseFor(arov1alpha1.InternetReachableFromMaster) &&
-				co.Status.Conditions.IsFalseFor(arov1alpha1.InternetReachableFromWorker), nil
+			return conditions.IsFalse(co.Status.Conditions, arov1alpha1.InternetReachableFromMaster) &&
+				conditions.IsFalse(co.Status.Conditions, arov1alpha1.InternetReachableFromWorker), nil
 		})
 		Expect(err).NotTo(HaveOccurred())
 	})
@@ -261,7 +262,7 @@ var _ = Describe("ARO Operator - Conditions", func() {
 
 			valid := true
 			for _, condition := range arov1alpha1.ClusterChecksTypes() {
-				if !co.Status.Conditions.IsTrueFor(condition) {
+				if !conditions.IsTrue(co.Status.Conditions, condition) {
 					valid = false
 				}
 			}