Repository: aquasecurity/starboard
Pull request #431 (2021-03-15 10:12:18)

refactor: Merge rs package with kube package
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/431?src=pr&el=h1) Report
> Merging [#431](https://codecov.io/gh/aquasecurity/starboard/pull/431?src=pr&el=desc) (f03de80) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/30c164c4989030b7f875b9d1bba34629ac747a91?el=desc) (30c164c) will **decrease** coverage by `37.80%`.
> The diff coverage is `18.86%`.

[![Impacted file tree graph](https://codecov.io/gh/aquasecurity/starboard/pull/431/graphs/tree.svg?width=650&height=150&src=pr&token=qqTGKAJ0Wb)](https://codecov.io/gh/aquasecurity/starboard/pull/431?src=pr&el=tree)

```diff
@@             Coverage Diff             @@
##             main     #431       +/-   ##
===========================================
- Coverage   68.64%   30.84%   -37.81%     
===========================================
  Files          57       56        -1     
  Lines        3346     3268       -78     
===========================================
- Hits         2297     1008     -1289     
- Misses        776     2199     +1423     
+ Partials      273       61      -212     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/431?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/cmd/get\_configaudit.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXRfY29uZmlnYXVkaXQuZ28=) | `0.00% <0.00%> (-28.31%)` | :arrow_down: |
| [pkg/cmd/get\_report.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXRfcmVwb3J0Lmdv) | `0.00% <0.00%> (-47.37%)` | :arrow_down: |
| [pkg/cmd/get\_vulnerabilities.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXRfdnVsbmVyYWJpbGl0aWVzLmdv) | `0.00% <0.00%> (-61.12%)` | :arrow_down: |
| [pkg/cmd/scan\_configaudit.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuX2NvbmZpZ2F1ZGl0Lmdv) | `0.00% <0.00%> (-55.00%)` | :arrow_down: |
| [pkg/cmd/scan\_vulnerabilities.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuX3Z1bG5lcmFiaWxpdGllcy5nbw==) | `0.00% <0.00%> (-66.67%)` | :arrow_down: |
| [pkg/kube/object.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL2t1YmUvb2JqZWN0Lmdv) | `31.25% <0.00%> (-38.38%)` | :arrow_down: |
| [pkg/report/html.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL3JlcG9ydC9odG1sLmdv) | `0.00% <0.00%> (ø)` | |
| [pkg/configauditreport/io.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL2NvbmZpZ2F1ZGl0cmVwb3J0L2lvLmdv) | `53.65% <83.33%> (-3.49%)` | :arrow_down: |
| [pkg/vulnerabilityreport/io.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL3Z1bG5lcmFiaWxpdHlyZXBvcnQvaW8uZ28=) | `53.48% <83.33%> (-21.52%)` | :arrow_down: |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXQuZ28=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [39 more](https://codecov.io/gh/aquasecurity/starboard/pull/431/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/431?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/431?src=pr&el=footer). Last update [30c164c...f03de80](https://codecov.io/gh/aquasecurity/starboard/pull/431?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/pkg/cmd/get_configaudit.go b/pkg/cmd/get_configaudit.go
index 6275dc5..ec7e71c 100644
--- a/pkg/cmd/get_configaudit.go
+++ b/pkg/cmd/get_configaudit.go
@@ -9,7 +9,6 @@ import (
 	"github.com/aquasecurity/starboard/pkg/starboard"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"k8s.io/client-go/kubernetes"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
@@ -40,10 +39,6 @@ NAME is the name of a particular Kubernetes workload.
 			if err != nil {
 				return err
 			}
-			kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
-			if err != nil {
-				return err
-			}
 			ns, _, err := cf.ToRawKubeConfigLoader().Namespace()
 			if err != nil {
 				return err
@@ -61,7 +56,7 @@ NAME is the name of a particular Kubernetes workload.
 			if err != nil {
 				return err
 			}
-			reader := configauditreport.NewReadWriter(kubeClient, kubeClientset)
+			reader := configauditreport.NewReadWriter(kubeClient)
 			report, err := reader.FindByOwnerInHierarchy(ctx, workload)
 			if err != nil {
 				return nil
diff --git a/pkg/cmd/get_report.go b/pkg/cmd/get_report.go
index 0d46b1d..dc736b1 100644
--- a/pkg/cmd/get_report.go
+++ b/pkg/cmd/get_report.go
@@ -10,7 +10,6 @@ import (
 	"github.com/aquasecurity/starboard/pkg/starboard"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"k8s.io/client-go/kubernetes"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
@@ -54,10 +53,6 @@ NAME is the name of a particular Kubernetes workload.
 			if err != nil {
 				return err
 			}
-			kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
-			if err != nil {
-				return err
-			}
 			kubeClient, err := client.New(kubeConfig, client.Options{Scheme: starboard.NewScheme()})
 			ns, _, err := cf.ToRawKubeConfigLoader().Namespace()
 			if err != nil {
@@ -81,7 +76,7 @@ NAME is the name of a particular Kubernetes workload.
 				kube.KindCronJob,
 				kube.KindJob,
 				kube.KindPod:
-				reporter := report.NewWorkloadReporter(clock, kubeClientset, kubeClient)
+				reporter := report.NewWorkloadReporter(clock, kubeClient)
 				return reporter.Generate(workload, outWriter)
 			case kube.KindNamespace:
 				reporter := report.NewNamespaceReporter(clock, kubeClient)
diff --git a/pkg/cmd/get_vulnerabilities.go b/pkg/cmd/get_vulnerabilities.go
index 3c3394a..a6bf84b 100644
--- a/pkg/cmd/get_vulnerabilities.go
+++ b/pkg/cmd/get_vulnerabilities.go
@@ -10,7 +10,6 @@ import (
 	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
-	"k8s.io/client-go/kubernetes"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
@@ -42,10 +41,6 @@ NAME is the name of a particular Kubernetes workload.
 			if err != nil {
 				return err
 			}
-			kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
-			if err != nil {
-				return err
-			}
 			scheme := starboard.NewScheme()
 			kubeClient, err := client.New(kubeConfig, client.Options{Scheme: scheme})
 			if err != nil {
@@ -64,7 +59,7 @@ NAME is the name of a particular Kubernetes workload.
 				return err
 			}
 
-			reader := vulnerabilityreport.NewReadWriter(kubeClient, kubeClientset)
+			reader := vulnerabilityreport.NewReadWriter(kubeClient)
 			items, err := reader.FindByOwnerInHierarchy(ctx, workload)
 			if err != nil {
 				return fmt.Errorf("list vulnerability reports: %v", err)
diff --git a/pkg/cmd/scan_configaudit.go b/pkg/cmd/scan_configaudit.go
index f89a9d4..1c3aecd 100644
--- a/pkg/cmd/scan_configaudit.go
+++ b/pkg/cmd/scan_configaudit.go
@@ -71,7 +71,7 @@ func ScanConfigAuditReports(buildInfo starboard.BuildInfo, cf *genericclioptions
 		if err != nil {
 			return err
 		}
-		writer := configauditreport.NewReadWriter(kubeClient, kubeClientset)
+		writer := configauditreport.NewReadWriter(kubeClient)
 		return writer.Write(ctx, report)
 	}
 }
diff --git a/pkg/cmd/scan_vulnerabilities.go b/pkg/cmd/scan_vulnerabilities.go
index f349025..263741e 100644
--- a/pkg/cmd/scan_vulnerabilities.go
+++ b/pkg/cmd/scan_vulnerabilities.go
@@ -107,7 +107,7 @@ func ScanVulnerabilityReports(buildInfo starboard.BuildInfo, cf *genericclioptio
 		if err != nil {
 			return err
 		}
-
-		return vulnerabilityreport.NewReadWriter(kubeClient, kubeClientset).Write(ctx, reports)
+		writer := vulnerabilityreport.NewReadWriter(kubeClient)
+		return writer.Write(ctx, reports)
 	}
 }
diff --git a/pkg/configauditreport/io.go b/pkg/configauditreport/io.go
index 52410b7..05bb549 100644
--- a/pkg/configauditreport/io.go
+++ b/pkg/configauditreport/io.go
@@ -4,11 +4,8 @@ import (
 	"context"
 	"fmt"
 
-	"k8s.io/client-go/kubernetes"
-
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"github.com/aquasecurity/starboard/pkg/kube"
-	"github.com/aquasecurity/starboard/pkg/kube/rs"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -41,24 +38,21 @@ type ReadWriter interface {
 }
 
 type readWriter struct {
-	client client.Client
-	// TODO Get rid of it once we refactor ReplicaSet resolver
-	clientset kubernetes.Interface
+	*kube.ObjectResolver
 }
 
 // NewReadWriter constructs a new ReadWriter which is using the client package
 // provided by the controller-runtime libraries for interacting with the
 // Kubernetes API server.
-func NewReadWriter(client client.Client, clientset kubernetes.Interface) ReadWriter {
+func NewReadWriter(client client.Client) ReadWriter {
 	return &readWriter{
-		client:    client,
-		clientset: clientset,
+		ObjectResolver: &kube.ObjectResolver{Client: client},
 	}
 }
 
 func (r *readWriter) Write(ctx context.Context, report v1alpha1.ConfigAuditReport) error {
 	var existing v1alpha1.ConfigAuditReport
-	err := r.client.Get(ctx, types.NamespacedName{
+	err := r.Get(ctx, types.NamespacedName{
 		Name:      report.Name,
 		Namespace: report.Namespace,
 	}, &existing)
@@ -68,11 +62,11 @@ func (r *readWriter) Write(ctx context.Context, report v1alpha1.ConfigAuditRepor
 		copied.Labels = report.Labels
 		copied.Report = report.Report
 
-		return r.client.Update(ctx, copied)
+		return r.Update(ctx, copied)
 	}
 
 	if errors.IsNotFound(err) {
-		return r.client.Create(ctx, &report)
+		return r.Create(ctx, &report)
 	}
 
 	return err
@@ -81,7 +75,7 @@ func (r *readWriter) Write(ctx context.Context, report v1alpha1.ConfigAuditRepor
 func (r *readWriter) FindByOwner(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error) {
 	var list v1alpha1.ConfigAuditReportList
 
-	err := r.client.List(ctx, &list, client.MatchingLabels{
+	err := r.List(ctx, &list, client.MatchingLabels{
 		kube.LabelResourceKind:      string(owner.Kind),
 		kube.LabelResourceNamespace: owner.Namespace,
 		kube.LabelResourceName:      owner.Name,
@@ -105,7 +99,7 @@ func (r *readWriter) FindByOwnerInHierarchy(ctx context.Context, owner kube.Obje
 
 	// no reports found for provided owner, look for reports in related replicaset
 	if report == nil && (owner.Kind == kube.KindDeployment || owner.Kind == kube.KindPod) {
-		rsName, err := rs.GetRelatedReplicasetName(ctx, owner, r.clientset)
+		rsName, err := r.GetRelatedReplicasetName(ctx, owner)
 		if err != nil {
 			return nil, fmt.Errorf("getting replicaset related to %s/%s: %w", owner.Kind, owner.Name, err)
 		}
diff --git a/pkg/kube/object.go b/pkg/kube/object.go
index b70f873..71df570 100644
--- a/pkg/kube/object.go
+++ b/pkg/kube/object.go
@@ -174,3 +174,59 @@ func (o *ObjectResolver) GetObjectFromPartialObject(ctx context.Context, workloa
 	}
 	return obj, nil
 }
+
+// GetRelatedReplicasetName attempts to find the replicaset that is associated with
+// the given owner. If the owner is a Deployment, it will look for a ReplicaSet
+// that is controlled by the Deployment. If the owner is a Pod, it will look for
+// the ReplicaSet that owns the Pod.
+func (o *ObjectResolver) GetRelatedReplicasetName(ctx context.Context, object Object) (string, error) {
+	switch object.Kind {
+	case KindDeployment:
+		return o.getActiveReplicaSetByDeployment(ctx, object)
+	case KindPod:
+		return o.getReplicaSetByPod(ctx, object)
+	}
+	return "", fmt.Errorf("can only get related ReplicaSet for Deployment or Pod, not %q", string(object.Kind))
+}
+
+func (o *ObjectResolver) getActiveReplicaSetByDeployment(ctx context.Context, object Object) (string, error) {
+	deploy := &appsv1.Deployment{}
+	err := o.Client.Get(ctx, types.NamespacedName{Namespace: object.Namespace, Name: object.Name}, deploy)
+	if err != nil {
+		return "", fmt.Errorf("getting deployment %q: %w", object.Namespace+"/"+object.Name, err)
+	}
+	var rsList appsv1.ReplicaSetList
+	err = o.Client.List(ctx, &rsList, client.MatchingLabelsSelector{
+		Selector: labels.SelectorFromSet(deploy.Spec.Selector.MatchLabels),
+	})
+	if err != nil {
+		return "", fmt.Errorf("listing replicasets for deployment %q: %w", object.Name, err)
+	}
+	if len(rsList.Items) == 0 {
+		return "", fmt.Errorf("no replicasets associated with deployment %q", object.Name)
+	}
+	for _, rs := range rsList.Items {
+		if deploy.Annotations["deployment.kubernetes.io/revision"] !=
+			rs.Annotations["deployment.kubernetes.io/revision"] {
+			continue
+		}
+		return rs.Name, nil
+	}
+	return "", fmt.Errorf("did not find an active replicaset associated with deployment %q", object.Name)
+}
+
+func (o *ObjectResolver) getReplicaSetByPod(ctx context.Context, object Object) (string, error) {
+	pod := &corev1.Pod{}
+	err := o.Client.Get(ctx, types.NamespacedName{Namespace: object.Namespace, Name: object.Name}, pod)
+	if err != nil {
+		return "", err
+	}
+	controller := metav1.GetControllerOf(pod)
+	if controller == nil {
+		return "", fmt.Errorf("did not find a controller for pod %q", object.Name)
+	}
+	if controller.Kind != "ReplicaSet" {
+		return "", fmt.Errorf("pod %q is controlled by a %q, want replicaset", object.Name, controller.Kind)
+	}
+	return controller.Name, nil
+}
diff --git a/pkg/kube/rs/rs.go b/pkg/kube/rs/rs.go
deleted file mode 100644
index c2e4762..0000000
--- a/pkg/kube/rs/rs.go
+++ /dev/null
@@ -1,64 +0,0 @@
-package rs
-
-import (
-	"context"
-	"fmt"
-
-	"github.com/aquasecurity/starboard/pkg/kube"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/client-go/kubernetes"
-)
-
-// GetRelatedReplicasetName attempts to find the replicaset that is associated with
-// the given owner. If the owner is a Deployment, it will look for a replicaset
-// that is controlled by the Deployment. If the owner is a Pod, it will look for
-// the replicaset that owns the Pod.
-func GetRelatedReplicasetName(ctx context.Context, object kube.Object, clientset kubernetes.Interface) (string, error) {
-	switch object.Kind {
-	case kube.KindDeployment:
-		return getActiveReplicaSetByDeployment(ctx, object, clientset)
-	case kube.KindPod:
-		return getReplicaSetByPod(ctx, object, clientset)
-	}
-	return "", fmt.Errorf("can only get related replicaset for deployment or pod, not %q", string(object.Kind))
-}
-
-func getActiveReplicaSetByDeployment(ctx context.Context, object kube.Object, clientset kubernetes.Interface) (string, error) {
-	deploy, err := clientset.AppsV1().Deployments(object.Namespace).Get(ctx, object.Name, metav1.GetOptions{})
-	if err != nil {
-		return "", fmt.Errorf("getting deployment %q: %w", object.Name, err)
-	}
-	rsList, err := clientset.AppsV1().ReplicaSets(object.Namespace).List(ctx, metav1.ListOptions{
-		LabelSelector: labels.SelectorFromSet(deploy.Spec.Selector.MatchLabels).String(),
-	})
-	if err != nil {
-		return "", fmt.Errorf("listing replicasets for deployment %q: %w", object.Name, err)
-	}
-	if len(rsList.Items) == 0 {
-		return "", fmt.Errorf("no replicasets associated with deployment %q", object.Name)
-	}
-	for _, rs := range rsList.Items {
-		if deploy.Annotations["deployment.kubernetes.io/revision"] !=
-			rs.Annotations["deployment.kubernetes.io/revision"] {
-			continue
-		}
-		return rs.Name, nil
-	}
-	return "", fmt.Errorf("did not find an active replicaset associated with deployment %q", object.Name)
-}
-
-func getReplicaSetByPod(ctx context.Context, object kube.Object, clientset kubernetes.Interface) (string, error) {
-	pod, err := clientset.CoreV1().Pods(object.Namespace).Get(ctx, object.Name, metav1.GetOptions{})
-	if err != nil {
-		return "", err
-	}
-	controller := metav1.GetControllerOf(pod)
-	if controller == nil {
-		return "", fmt.Errorf("did not find a controller for pod %q", object.Name)
-	}
-	if controller.Kind != "ReplicaSet" {
-		return "", fmt.Errorf("pod %q is controlled by a %q, want replicaset", object.Name, controller.Kind)
-	}
-	return controller.Name, nil
-}
diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go
index b2e0fe7..dcac8cf 100644
--- a/pkg/operator/operator.go
+++ b/pkg/operator/operator.go
@@ -136,7 +136,7 @@ func Run(buildInfo starboard.BuildInfo, operatorConfig etc.Config) error {
 		LogsReader:     logsReader,
 		SecretsReader:  secretsReader,
 		Plugin:         vulnerabilityReportPlugin,
-		ReadWriter:     vulnerabilityreport.NewReadWriter(mgr.GetClient(), kubeClientset),
+		ReadWriter:     vulnerabilityreport.NewReadWriter(mgr.GetClient()),
 	}).SetupWithManager(mgr); err != nil {
 		return fmt.Errorf("unable to setup vulnerabilityreport reconciler: %w", err)
 	}
@@ -154,7 +154,7 @@ func Run(buildInfo starboard.BuildInfo, operatorConfig etc.Config) error {
 		LimitChecker:   limitChecker,
 		LogsReader:     logsReader,
 		Plugin:         configAuditReportPlugin,
-		ReadWriter:     configauditreport.NewReadWriter(mgr.GetClient(), kubeClientset),
+		ReadWriter:     configauditreport.NewReadWriter(mgr.GetClient()),
 	}).SetupWithManager(mgr); err != nil {
 		return fmt.Errorf("unable to setup configauditreport reconciler: %w", err)
 	}
diff --git a/pkg/report/html.go b/pkg/report/html.go
index 285152e..1c36f8b 100644
--- a/pkg/report/html.go
+++ b/pkg/report/html.go
@@ -14,7 +14,6 @@ import (
 	"github.com/aquasecurity/starboard/pkg/report/templates"
 	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/kubernetes"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
@@ -24,11 +23,11 @@ type workloadReporter struct {
 	configAuditReportsReader   configauditreport.ReadWriter
 }
 
-func NewWorkloadReporter(clock ext.Clock, kubeClientset kubernetes.Interface, client client.Client) WorkloadReporter {
+func NewWorkloadReporter(clock ext.Clock, client client.Client) WorkloadReporter {
 	return &workloadReporter{
 		clock:                      clock,
-		vulnerabilityReportsReader: vulnerabilityreport.NewReadWriter(client, kubeClientset),
-		configAuditReportsReader:   configauditreport.NewReadWriter(client, kubeClientset),
+		vulnerabilityReportsReader: vulnerabilityreport.NewReadWriter(client),
+		configAuditReportsReader:   configauditreport.NewReadWriter(client),
 	}
 }
 
diff --git a/pkg/vulnerabilityreport/io.go b/pkg/vulnerabilityreport/io.go
index dad92ed..564ece8 100644
--- a/pkg/vulnerabilityreport/io.go
+++ b/pkg/vulnerabilityreport/io.go
@@ -6,10 +6,8 @@ import (
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"github.com/aquasecurity/starboard/pkg/kube"
-	"github.com/aquasecurity/starboard/pkg/kube/rs"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/types"
-	"k8s.io/client-go/kubernetes"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
@@ -41,18 +39,15 @@ type ReadWriter interface {
 }
 
 type readWriter struct {
-	client client.Client
-	// TODO Get rid of it once we refactor ReplicaSet resolver
-	clientset kubernetes.Interface
+	*kube.ObjectResolver
 }
 
 // NewReadWriter constructs a new ReadWriter which is using the client package
 // provided by the controller-runtime libraries for interacting with the
 // Kubernetes API server.
-func NewReadWriter(client client.Client, clientset kubernetes.Interface) ReadWriter {
+func NewReadWriter(client client.Client) ReadWriter {
 	return &readWriter{
-		client:    client,
-		clientset: clientset,
+		ObjectResolver: &kube.ObjectResolver{Client: client},
 	}
 }
 
@@ -68,7 +63,7 @@ func (r *readWriter) Write(ctx context.Context, reports []v1alpha1.Vulnerability
 
 func (r *readWriter) createOrUpdate(ctx context.Context, report v1alpha1.VulnerabilityReport) error {
 	var existing v1alpha1.VulnerabilityReport
-	err := r.client.Get(ctx, types.NamespacedName{
+	err := r.Get(ctx, types.NamespacedName{
 		Name:      report.Name,
 		Namespace: report.Namespace,
 	}, &existing)
@@ -78,11 +73,11 @@ func (r *readWriter) createOrUpdate(ctx context.Context, report v1alpha1.Vulnera
 		copied.Labels = report.Labels
 		copied.Report = report.Report
 
-		return r.client.Update(ctx, copied)
+		return r.Update(ctx, copied)
 	}
 
 	if errors.IsNotFound(err) {
-		return r.client.Create(ctx, &report)
+		return r.Create(ctx, &report)
 	}
 
 	return err
@@ -91,7 +86,7 @@ func (r *readWriter) createOrUpdate(ctx context.Context, report v1alpha1.Vulnera
 func (r *readWriter) FindByOwner(ctx context.Context, owner kube.Object) ([]v1alpha1.VulnerabilityReport, error) {
 	var list v1alpha1.VulnerabilityReportList
 
-	err := r.client.List(ctx, &list, client.MatchingLabels{
+	err := r.List(ctx, &list, client.MatchingLabels{
 		kube.LabelResourceKind:      string(owner.Kind),
 		kube.LabelResourceNamespace: owner.Namespace,
 		kube.LabelResourceName:      owner.Name,
@@ -111,7 +106,7 @@ func (r *readWriter) FindByOwnerInHierarchy(ctx context.Context, owner kube.Obje
 
 	// no reports found for provided owner, look for reports in related replicaset
 	if len(reports) == 0 && (owner.Kind == kube.KindDeployment || owner.Kind == kube.KindPod) {
-		rsName, err := rs.GetRelatedReplicasetName(ctx, owner, r.clientset)
+		rsName, err := r.GetRelatedReplicasetName(ctx, owner)
 		if err != nil {
 			return nil, fmt.Errorf("getting replicaset related to %s/%s: %w", owner.Kind, owner.Name, err)
 		}


Test patch:
diff --git a/itest/starboard-operator/suite_test.go b/itest/starboard-operator/suite_test.go
index 95ec103..045c933 100644
--- a/itest/starboard-operator/suite_test.go
+++ b/itest/starboard-operator/suite_test.go
@@ -1,7 +1,6 @@
 package starboard_operator
 
 import (
-	"k8s.io/apimachinery/pkg/runtime"
 	"path/filepath"
 	"testing"
 
@@ -12,6 +11,7 @@ import (
 	"github.com/aquasecurity/starboard/pkg/starboard"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
+	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/client-go/kubernetes"
 	"k8s.io/utils/pointer"
 	ctrl "sigs.k8s.io/controller-runtime"
diff --git a/pkg/configauditreport/io_test.go b/pkg/configauditreport/io_test.go
index e806d53..c58c189 100644
--- a/pkg/configauditreport/io_test.go
+++ b/pkg/configauditreport/io_test.go
@@ -12,7 +12,6 @@ import (
 	"github.com/stretchr/testify/require"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	fakeKubernetes "k8s.io/client-go/kubernetes/fake"
 	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
@@ -22,7 +21,7 @@ func TestReadWriter(t *testing.T) {
 
 	t.Run("Should create ConfigAuditReport", func(t *testing.T) {
 		client := fake.NewClientBuilder().WithScheme(kubernetesScheme).Build()
-		readWriter := configauditreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
+		readWriter := configauditreport.NewReadWriter(client)
 		err := readWriter.Write(context.TODO(), v1alpha1.ConfigAuditReport{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      "deployment-app",
@@ -89,7 +88,7 @@ func TestReadWriter(t *testing.T) {
 				},
 			},
 		}).Build()
-		readWriter := configauditreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
+		readWriter := configauditreport.NewReadWriter(client)
 		err := readWriter.Write(context.TODO(), v1alpha1.ConfigAuditReport{
 			ObjectMeta: metav1.ObjectMeta{
 				Name:      "deployment-app",
@@ -164,7 +163,7 @@ func TestReadWriter(t *testing.T) {
 			Report: v1alpha1.ConfigAuditResult{},
 		}).Build()
 
-		readWriter := configauditreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
+		readWriter := configauditreport.NewReadWriter(client)
 		found, err := readWriter.FindByOwner(context.TODO(), kube.Object{
 			Kind:      kube.KindDeployment,
 			Name:      "my-deploy",
diff --git a/pkg/kube/object_test.go b/pkg/kube/object_test.go
index 4012889..53c1906 100644
--- a/pkg/kube/object_test.go
+++ b/pkg/kube/object_test.go
@@ -1,19 +1,24 @@
 package kube_test
 
 import (
+	"context"
 	"errors"
 	"testing"
 
 	"github.com/aquasecurity/starboard/pkg/kube"
+	"github.com/aquasecurity/starboard/pkg/starboard"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	appsv1 "k8s.io/api/apps/v1"
 	batchv1 "k8s.io/api/batch/v1"
 	batchv1beta1 "k8s.io/api/batch/v1beta1"
 	corev1 "k8s.io/api/core/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/types"
+	"k8s.io/utils/pointer"
 	"sigs.k8s.io/controller-runtime/pkg/client"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
 func TestObjectFromLabelsSet(t *testing.T) {
@@ -332,3 +337,119 @@ func TestGetPodSpec(t *testing.T) {
 		})
 	}
 }
+
+func TestObjectResolver_GetRelatedReplicasetName(t *testing.T) {
+
+	instance := &kube.ObjectResolver{Client: fake.NewClientBuilder().WithScheme(starboard.NewScheme()).WithObjects(
+		&appsv1.Deployment{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "nginx",
+				Namespace: corev1.NamespaceDefault,
+				Labels: map[string]string{
+					"app": "nginx",
+				},
+				Annotations: map[string]string{
+					"deployment.kubernetes.io/revision": "2",
+				},
+			},
+			Spec: appsv1.DeploymentSpec{
+				Selector: &metav1.LabelSelector{
+					MatchLabels: map[string]string{
+						"app": "nginx",
+					},
+				},
+			},
+		},
+		&appsv1.ReplicaSet{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "nginx-7ff78f74b9",
+				Namespace: corev1.NamespaceDefault,
+				Labels: map[string]string{
+					"app":               "nginx",
+					"pod-template-hash": "7ff78f74b9",
+				},
+				Annotations: map[string]string{
+					"deployment.kubernetes.io/revision": "1",
+				},
+			},
+			Spec: appsv1.ReplicaSetSpec{
+				Selector: &metav1.LabelSelector{
+					MatchLabels: map[string]string{
+						"app":               "nginx",
+						"pod-template-hash": "7ff78f74b9",
+					},
+				},
+			},
+		},
+		&appsv1.ReplicaSet{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "nginx-549f5fcb58",
+				Namespace: corev1.NamespaceDefault,
+				Labels: map[string]string{
+					"app":               "nginx",
+					"pod-template-hash": "549f5fcb58",
+				},
+				Annotations: map[string]string{
+					"deployment.kubernetes.io/revision": "2",
+				},
+			},
+			Spec: appsv1.ReplicaSetSpec{
+				Selector: &metav1.LabelSelector{
+					MatchLabels: map[string]string{
+						"app":               "nginx",
+						"pod-template-hash": "549f5fcb58",
+					},
+				},
+			},
+		},
+		&corev1.Pod{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "nginx-549f5fcb58-7cr5b",
+				Namespace: corev1.NamespaceDefault,
+				Labels: map[string]string{
+					"app":               "nginx",
+					"pod-hash-template": "549f5fcb58",
+				},
+				OwnerReferences: []metav1.OwnerReference{
+					{
+						APIVersion:         "apps/v1",
+						Kind:               "ReplicaSet",
+						Name:               "nginx-549f5fcb58",
+						Controller:         pointer.BoolPtr(true),
+						BlockOwnerDeletion: pointer.BoolPtr(true),
+					},
+				},
+			},
+		},
+	).Build()}
+
+	t.Run("Should return error for unsupported kind", func(t *testing.T) {
+		_, err := instance.GetRelatedReplicasetName(context.Background(), kube.Object{
+			Kind:      kube.KindStatefulSet,
+			Name:      "statefulapp",
+			Namespace: corev1.NamespaceDefault,
+		})
+		require.EqualError(t, err, "can only get related ReplicaSet for Deployment or Pod, not \"StatefulSet\"")
+	})
+
+	t.Run("Should return ReplicaSet name for the specified Deployment", func(t *testing.T) {
+		name, err := instance.GetRelatedReplicasetName(context.Background(), kube.Object{
+			Kind:      kube.KindDeployment,
+			Name:      "nginx",
+			Namespace: corev1.NamespaceDefault,
+		})
+		require.NoError(t, err)
+		assert.Equal(t, "nginx-549f5fcb58", name)
+	})
+
+	t.Run("Should return ReplicaSet name for the specified Deployment", func(t *testing.T) {
+		name, err := instance.GetRelatedReplicasetName(context.Background(), kube.Object{
+			Kind:      kube.KindPod,
+			Name:      "nginx-549f5fcb58-7cr5b",
+			Namespace: corev1.NamespaceDefault,
+		})
+		require.NoError(t, err)
+		assert.Equal(t, "nginx-549f5fcb58", name)
+	})
+
+}
diff --git a/pkg/vulnerabilityreport/io_test.go b/pkg/vulnerabilityreport/io_test.go
index b9fdcce..8018352 100644
--- a/pkg/vulnerabilityreport/io_test.go
+++ b/pkg/vulnerabilityreport/io_test.go
@@ -12,17 +12,16 @@ import (
 	"github.com/stretchr/testify/require"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/types"
-	fakeKubernetes "k8s.io/client-go/kubernetes/fake"
-	controllerFake "sigs.k8s.io/controller-runtime/pkg/client/fake"
+	"sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
-func TestControllerRuntimeReadWriter(t *testing.T) {
+func TestNewReadWriter(t *testing.T) {
 
 	kubernetesScheme := starboard.NewScheme()
 
 	t.Run("Should create VulnerabilityReports", func(t *testing.T) {
-		client := controllerFake.NewClientBuilder().WithScheme(kubernetesScheme).Build()
-		readWriter := vulnerabilityreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
+		client := fake.NewClientBuilder().WithScheme(kubernetesScheme).Build()
+		readWriter := vulnerabilityreport.NewReadWriter(client)
 		err := readWriter.Write(context.TODO(), []v1alpha1.VulnerabilityReport{
 			{
 				ObjectMeta: metav1.ObjectMeta{
@@ -92,7 +91,7 @@ func TestControllerRuntimeReadWriter(t *testing.T) {
 	})
 
 	t.Run("Should update VulnerabilityReports", func(t *testing.T) {
-		client := controllerFake.NewClientBuilder().WithScheme(kubernetesScheme).WithObjects(
+		client := fake.NewClientBuilder().WithScheme(kubernetesScheme).WithObjects(
 			&v1alpha1.VulnerabilityReport{
 				ObjectMeta: metav1.ObjectMeta{
 					Name:      "deployment-app1-container1",
@@ -119,7 +118,7 @@ func TestControllerRuntimeReadWriter(t *testing.T) {
 					},
 				},
 			}).Build()
-		readWriter := vulnerabilityreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
+		readWriter := vulnerabilityreport.NewReadWriter(client)
 		err := readWriter.Write(context.TODO(), []v1alpha1.VulnerabilityReport{
 			{
 				ObjectMeta: metav1.ObjectMeta{
@@ -201,7 +200,7 @@ func TestControllerRuntimeReadWriter(t *testing.T) {
 	})
 
 	t.Run("Should find VulnerabilityReports", func(t *testing.T) {
-		client := controllerFake.NewClientBuilder().WithScheme(kubernetesScheme).WithObjects(&v1alpha1.VulnerabilityReport{
+		client := fake.NewClientBuilder().WithScheme(kubernetesScheme).WithObjects(&v1alpha1.VulnerabilityReport{
 			ObjectMeta: metav1.ObjectMeta{
 				Namespace: "my-namespace",
 				Name:      "deployment-my-deploy-my-container-01",
@@ -239,7 +238,7 @@ func TestControllerRuntimeReadWriter(t *testing.T) {
 			Report: v1alpha1.VulnerabilityScanResult{},
 		}).Build()
 
-		readWriter := vulnerabilityreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
+		readWriter := vulnerabilityreport.NewReadWriter(client)
 		list, err := readWriter.FindByOwner(context.TODO(), kube.Object{
 			Kind:      kube.KindDeployment,
 			Name:      "my-deploy",