Repository: aquasecurity/starboard
Pull request #403 (2021-02-17 11:04:25)

refactor: vulnerabilityreport.ReadWriter to use controller-runtime Client
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/403?src=pr&el=h1) Report
> Merging [#403](https://codecov.io/gh/aquasecurity/starboard/pull/403?src=pr&el=desc) (7aee966) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/5d98f6375558478d55be719f4ac09533a21e67fb?el=desc) (5d98f63) will **decrease** coverage by `39.45%`.
> The diff coverage is `5.55%`.

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

```diff
@@             Coverage Diff             @@
##             main     #403       +/-   ##
===========================================
- Coverage   67.87%   28.41%   -39.46%     
===========================================
  Files          59       58        -1     
  Lines        3128     3009      -119     
===========================================
- Hits         2123      855     -1268     
- Misses        743     2095     +1352     
+ Partials      262       59      -203     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/403?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/cmd/get\_report.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXRfcmVwb3J0Lmdv) | `0.00% <0.00%> (-29.04%)` | :arrow_down: |
| [pkg/cmd/get\_vulnerabilities.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXRfdnVsbmVyYWJpbGl0aWVzLmdv) | `0.00% <0.00%> (-59.62%)` | :arrow_down: |
| [pkg/cmd/scan\_vulnerabilities.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuX3Z1bG5lcmFiaWxpdGllcy5nbw==) | `0.00% <0.00%> (-67.75%)` | :arrow_down: |
| [pkg/configauditreport/io.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL2NvbmZpZ2F1ZGl0cmVwb3J0L2lvLmdv) | `54.76% <ø> (-2.39%)` | :arrow_down: |
| [pkg/report/html.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL3JlcG9ydC9odG1sLmdv) | `0.00% <0.00%> (ø)` | |
| [pkg/vulnerabilityreport/io.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL3Z1bG5lcmFiaWxpdHlyZXBvcnQvaW8uZ28=) | `54.54% <11.76%> (-26.54%)` | :arrow_down: |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXQuZ28=) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/find.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL2NtZC9maW5kLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/root.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL2NtZC9yb290Lmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/scan.go](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [36 more](https://codecov.io/gh/aquasecurity/starboard/pull/403/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/403?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/403?src=pr&el=footer). Last update [5d98f63...2bb311e](https://codecov.io/gh/aquasecurity/starboard/pull/403?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).


Patch:
diff --git a/go.sum b/go.sum
index 7563fd4..5cf5830 100644
--- a/go.sum
+++ b/go.sum
@@ -443,6 +443,7 @@ github.com/hashicorp/go-sockaddr v1.0.0/go.mod h1:7Xibr9yA9JjQq1JpNB2Vw7kxv8xerX
 github.com/hashicorp/go-syslog v1.0.0/go.mod h1:qPfqrKkXGihmCqbJM2mZgkZGvKG1dFdvsLplgctolz4=
 github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
 github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/go-version v1.2.0 h1:3vNe/fWF5CBgRIguda1meWhsZHy3m8gCJ5wx+dIzX/E=
 github.com/hashicorp/go-version v1.2.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
 github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
diff --git a/pkg/cmd/get_report.go b/pkg/cmd/get_report.go
index 83a8728..c94c810 100644
--- a/pkg/cmd/get_report.go
+++ b/pkg/cmd/get_report.go
@@ -4,7 +4,6 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
 	"github.com/aquasecurity/starboard/pkg/report"
 	"github.com/aquasecurity/starboard/pkg/starboard"
 	"github.com/spf13/cobra"
@@ -29,10 +28,6 @@ NAME is the name of a particular Kubernetes workload.
 			if err != nil {
 				return err
 			}
-			starboardClientset, err := versioned.NewForConfig(kubeConfig)
-			if err != nil {
-				return err
-			}
 			kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
 			if err != nil {
 				return err
@@ -51,7 +46,7 @@ NAME is the name of a particular Kubernetes workload.
 				return err
 			}
 
-			reporter := report.NewHTMLReporter(starboardClientset, kubeClientset, kubeClient)
+			reporter := report.NewHTMLReporter(kubeClientset, kubeClient)
 			return reporter.GenerateReport(workload, outWriter)
 		},
 	}
diff --git a/pkg/cmd/get_vulnerabilities.go b/pkg/cmd/get_vulnerabilities.go
index b85b5cf..3c3394a 100644
--- a/pkg/cmd/get_vulnerabilities.go
+++ b/pkg/cmd/get_vulnerabilities.go
@@ -5,15 +5,13 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/aquasecurity/starboard/pkg/starboard"
-
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/starboard"
 	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
-
-	clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"k8s.io/client-go/kubernetes"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 func NewGetVulnerabilitiesCmd(executable string, cf *genericclioptions.ConfigFlags, outWriter io.Writer) *cobra.Command {
@@ -40,15 +38,16 @@ NAME is the name of a particular Kubernetes workload.
 		RunE: func(cmd *cobra.Command, args []string) error {
 			ctx := context.Background()
 
-			config, err := cf.ToRESTConfig()
+			kubeConfig, err := cf.ToRESTConfig()
 			if err != nil {
 				return err
 			}
-			starboardClientset, err := clientset.NewForConfig(config)
+			kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
 			if err != nil {
 				return err
 			}
-			kubernetesClientset, err := kubernetes.NewForConfig(config)
+			scheme := starboard.NewScheme()
+			kubeClient, err := client.New(kubeConfig, client.Options{Scheme: scheme})
 			if err != nil {
 				return err
 			}
@@ -65,7 +64,8 @@ NAME is the name of a particular Kubernetes workload.
 				return err
 			}
 
-			items, err := vulnerabilityreport.NewReadWriter(starboardClientset, kubernetesClientset).FindByOwner(ctx, workload)
+			reader := vulnerabilityreport.NewReadWriter(kubeClient, kubeClientset)
+			items, err := reader.FindByOwnerInHierarchy(ctx, workload)
 			if err != nil {
 				return fmt.Errorf("list vulnerability reports: %v", err)
 			}
diff --git a/pkg/cmd/scan_vulnerabilities.go b/pkg/cmd/scan_vulnerabilities.go
index af6a0de..958f63a 100644
--- a/pkg/cmd/scan_vulnerabilities.go
+++ b/pkg/cmd/scan_vulnerabilities.go
@@ -5,12 +5,12 @@ import (
 	"fmt"
 
 	"github.com/aquasecurity/starboard/pkg/config"
-	apis "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
 	"github.com/aquasecurity/starboard/pkg/starboard"
 	"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"
 )
 
 const (
@@ -77,15 +77,20 @@ func ScanVulnerabilityReports(buildInfo starboard.BuildInfo, cf *genericclioptio
 		if err != nil {
 			return err
 		}
-		kubernetesConfig, err := cf.ToRESTConfig()
+		kubeConfig, err := cf.ToRESTConfig()
 		if err != nil {
 			return err
 		}
-		kubernetesClientset, err := kubernetes.NewForConfig(kubernetesConfig)
+		kubeClientset, err := kubernetes.NewForConfig(kubeConfig)
 		if err != nil {
 			return err
 		}
-		starboardConfig, err := starboard.NewConfigManager(kubernetesClientset, starboard.NamespaceName).Read(ctx)
+		scheme := starboard.NewScheme()
+		kubeClient, err := client.New(kubeConfig, client.Options{Scheme: scheme})
+		if err != nil {
+			return err
+		}
+		starboardConfig, err := starboard.NewConfigManager(kubeClientset, starboard.NamespaceName).Read(ctx)
 		if err != nil {
 			return err
 		}
@@ -98,18 +103,14 @@ func ScanVulnerabilityReports(buildInfo starboard.BuildInfo, cf *genericclioptio
 			return err
 		}
 		reports, err := vulnerabilityreport.NewScanner(
-			starboard.NewScheme(),
-			kubernetesClientset,
+			scheme,
+			kubeClientset,
 			opts,
 			plugin).Scan(ctx, workload)
 		if err != nil {
 			return err
 		}
 
-		starboardClientset, err := apis.NewForConfig(kubernetesConfig)
-		if err != nil {
-			return err
-		}
-		return vulnerabilityreport.NewReadWriter(starboardClientset, kubernetesClientset).Write(ctx, reports)
+		return vulnerabilityreport.NewReadWriter(kubeClient, kubeClientset).Write(ctx, reports)
 	}
 }
diff --git a/pkg/configauditreport/io.go b/pkg/configauditreport/io.go
index 50683e7..52410b7 100644
--- a/pkg/configauditreport/io.go
+++ b/pkg/configauditreport/io.go
@@ -3,6 +3,7 @@ package configauditreport
 import (
 	"context"
 	"fmt"
+
 	"k8s.io/client-go/kubernetes"
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
@@ -26,9 +27,9 @@ type Writer interface {
 // kube.Object or nil if the report is not found.
 //
 // FindByOwnerInHierarchy is similar to FindByOwner except that it tries to lookup
-// the v1alpha1.ConfigAuditReport objects owned by related Kubernetes objects.
-// For example, if the given owner is a Deployment, but a report is owned
-// by the active ReplicaSet (current revision) this method will return the report.
+// a v1alpha1.ConfigAuditReport object owned by related Kubernetes objects.
+// For example, if the given owner is a Deployment, but a report is owned by the
+// active ReplicaSet (current revision) this method will return the report.
 type Reader interface {
 	FindByOwner(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error)
 	FindByOwnerInHierarchy(ctx context.Context, owner kube.Object) (*v1alpha1.ConfigAuditReport, error)
diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go
index bf62f73..8108fa9 100644
--- a/pkg/operator/operator.go
+++ b/pkg/operator/operator.go
@@ -129,7 +129,7 @@ func Run(buildInfo starboard.BuildInfo, operatorConfig etc.Config) error {
 		LogsReader:    logsReader,
 		SecretsReader: secretsReader,
 		Plugin:        vulnerabilityReportPlugin,
-		ReadWriter:    vulnerabilityreport.NewControllerRuntimeReadWriter(mgr.GetClient()),
+		ReadWriter:    vulnerabilityreport.NewReadWriter(mgr.GetClient(), kubeClientset),
 	}).SetupWithManager(mgr); err != nil {
 		return fmt.Errorf("unable to setup vulnerabilityreport reconciler: %w", err)
 	}
diff --git a/pkg/report/html.go b/pkg/report/html.go
index bdaa726..793404b 100644
--- a/pkg/report/html.go
+++ b/pkg/report/html.go
@@ -8,7 +8,6 @@ import (
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"github.com/aquasecurity/starboard/pkg/configauditreport"
-	"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
 	"github.com/aquasecurity/starboard/pkg/kube"
 	"github.com/aquasecurity/starboard/pkg/report/templates"
 	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
@@ -21,9 +20,9 @@ type htmlReporter struct {
 	configAuditReportsReader   configauditreport.ReadWriter
 }
 
-func NewHTMLReporter(starboardClientset versioned.Interface, kubeClientset kubernetes.Interface, client client.Client) Reporter {
+func NewHTMLReporter(kubeClientset kubernetes.Interface, client client.Client) Reporter {
 	return &htmlReporter{
-		vulnerabilityReportsReader: vulnerabilityreport.NewReadWriter(starboardClientset, kubeClientset),
+		vulnerabilityReportsReader: vulnerabilityreport.NewReadWriter(client, kubeClientset),
 		configAuditReportsReader:   configauditreport.NewReadWriter(client, kubeClientset),
 	}
 }
@@ -34,7 +33,7 @@ func (h *htmlReporter) GenerateReport(workload kube.Object, writer io.Writer) er
 	if err != nil {
 		return err
 	}
-	vulnerabilityReports, err := h.vulnerabilityReportsReader.FindByOwner(ctx, workload)
+	vulnerabilityReports, err := h.vulnerabilityReportsReader.FindByOwnerInHierarchy(ctx, workload)
 	if err != nil {
 		return err
 	}
diff --git a/pkg/vulnerabilityreport/io.go b/pkg/vulnerabilityreport/io.go
index d3e0839..dad92ed 100644
--- a/pkg/vulnerabilityreport/io.go
+++ b/pkg/vulnerabilityreport/io.go
@@ -5,15 +5,11 @@ import (
 	"fmt"
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
-	"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
 	"github.com/aquasecurity/starboard/pkg/kube"
 	"github.com/aquasecurity/starboard/pkg/kube/rs"
 	"k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/client-go/kubernetes"
-	"k8s.io/klog"
 	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
@@ -25,12 +21,18 @@ type Writer interface {
 	Write(context.Context, []v1alpha1.VulnerabilityReport) error
 }
 
-// Reader is the interface that wraps basic FindByOwner method.
+// Reader is the interface that wraps methods for finding v1alpha1.VulnerabilityReport objects.
 //
 // FindByOwner returns the slice of v1alpha1.VulnerabilityReport instances
 // owned by the given kube.Object or an empty slice if the reports are not found.
+//
+// FindByOwnerInHierarchy is similar to FindByOwner except it tries to lookup
+// v1alpha1.VulnerabilityReport objects owned by related Kubernetes objects.
+// For example, if the given owner is a Deployment, but reports are owned by the
+// active ReplicaSet (current revision) this method will return the reports.
 type Reader interface {
 	FindByOwner(context.Context, kube.Object) ([]v1alpha1.VulnerabilityReport, error)
+	FindByOwnerInHierarchy(ctx context.Context, object kube.Object) ([]v1alpha1.VulnerabilityReport, error)
 }
 
 type ReadWriter interface {
@@ -39,16 +41,18 @@ type ReadWriter interface {
 }
 
 type readWriter struct {
-	starboardClientset  versioned.Interface
-	kubernetesClientset kubernetes.Interface
+	client client.Client
+	// TODO Get rid of it once we refactor ReplicaSet resolver
+	clientset kubernetes.Interface
 }
 
-// NewReadWriter constructs a new ReadWriter which is using the client-go
-// module for interacting with the Kubernetes API server.
-func NewReadWriter(starboardClientset versioned.Interface, kubernetesClientset kubernetes.Interface) ReadWriter {
+// 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 {
 	return &readWriter{
-		starboardClientset:  starboardClientset,
-		kubernetesClientset: kubernetesClientset,
+		client:    client,
+		clientset: clientset,
 	}
 }
 
@@ -63,86 +67,6 @@ func (r *readWriter) Write(ctx context.Context, reports []v1alpha1.Vulnerability
 }
 
 func (r *readWriter) createOrUpdate(ctx context.Context, report v1alpha1.VulnerabilityReport) error {
-	existing, err := r.starboardClientset.AquasecurityV1alpha1().VulnerabilityReports(report.Namespace).
-		Get(ctx, report.Name, metav1.GetOptions{})
-
-	if err == nil {
-		klog.V(3).Infof("Updating VulnerabilityReport %q", report.Namespace+"/"+report.Name)
-		deepCopy := existing.DeepCopy()
-		deepCopy.Labels = report.Labels
-		deepCopy.Report = report.Report
-
-		_, err = r.starboardClientset.AquasecurityV1alpha1().VulnerabilityReports(report.Namespace).
-			Update(ctx, deepCopy, metav1.UpdateOptions{})
-		return err
-	}
-
-	if errors.IsNotFound(err) {
-		klog.V(3).Infof("Creating VulnerabilityReport %q", report.Namespace+"/"+report.Name)
-		_, err = r.starboardClientset.AquasecurityV1alpha1().VulnerabilityReports(report.Namespace).
-			Create(ctx, &report, metav1.CreateOptions{})
-		return err
-	}
-
-	return err
-}
-
-func (r *readWriter) FindByOwner(ctx context.Context, owner kube.Object) ([]v1alpha1.VulnerabilityReport, error) {
-	list, err := r.starboardClientset.AquasecurityV1alpha1().VulnerabilityReports(owner.Namespace).List(ctx, metav1.ListOptions{
-		LabelSelector: labels.Set{
-			kube.LabelResourceKind:      string(owner.Kind),
-			kube.LabelResourceName:      owner.Name,
-			kube.LabelResourceNamespace: owner.Namespace,
-		}.String(),
-	})
-	if err != nil {
-		return nil, err
-	}
-	// no reports found for provided owner, look for reports in related replicaset
-	if len(list.Items) == 0 && (owner.Kind == kube.KindDeployment || owner.Kind == kube.KindPod) {
-		rsName, err := rs.GetRelatedReplicasetName(ctx, owner, r.kubernetesClientset)
-		if err != nil {
-			return nil, fmt.Errorf("getting replicaset related to %s/%s: %w", owner.Kind, owner.Name, err)
-		}
-		list, err = r.starboardClientset.AquasecurityV1alpha1().VulnerabilityReports(owner.Namespace).List(ctx, metav1.ListOptions{
-			LabelSelector: labels.Set{
-				kube.LabelResourceKind:      string(kube.KindReplicaSet),
-				kube.LabelResourceName:      rsName,
-				kube.LabelResourceNamespace: owner.Namespace,
-			}.String(),
-		})
-		if err != nil {
-			return nil, err
-		}
-	}
-
-	return list.DeepCopy().Items, nil
-}
-
-type crReadWriter struct {
-	client client.Client
-}
-
-// NewControllerRuntimeReadWriter constructs a new ReadWriter which is
-// using the client package provided by the controller-runtime libraries for
-// interacting with the Kubernetes API server.
-func NewControllerRuntimeReadWriter(client client.Client) ReadWriter {
-	return &crReadWriter{
-		client: client,
-	}
-}
-
-func (r *crReadWriter) Write(ctx context.Context, reports []v1alpha1.VulnerabilityReport) error {
-	for _, report := range reports {
-		err := r.createOrUpdate(ctx, report)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (r *crReadWriter) createOrUpdate(ctx context.Context, report v1alpha1.VulnerabilityReport) error {
 	var existing v1alpha1.VulnerabilityReport
 	err := r.client.Get(ctx, types.NamespacedName{
 		Name:      report.Name,
@@ -164,7 +88,7 @@ func (r *crReadWriter) createOrUpdate(ctx context.Context, report v1alpha1.Vulne
 	return err
 }
 
-func (r *crReadWriter) FindByOwner(ctx context.Context, owner kube.Object) ([]v1alpha1.VulnerabilityReport, error) {
+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{
@@ -178,3 +102,28 @@ func (r *crReadWriter) FindByOwner(ctx context.Context, owner kube.Object) ([]v1
 
 	return list.DeepCopy().Items, nil
 }
+
+func (r *readWriter) FindByOwnerInHierarchy(ctx context.Context, owner kube.Object) ([]v1alpha1.VulnerabilityReport, error) {
+	reports, err := r.FindByOwner(ctx, owner)
+	if err != nil {
+		return nil, err
+	}
+
+	// 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)
+		if err != nil {
+			return nil, fmt.Errorf("getting replicaset related to %s/%s: %w", owner.Kind, owner.Name, err)
+		}
+		reports, err = r.FindByOwner(ctx, kube.Object{
+			Kind:      kube.KindReplicaSet,
+			Name:      rsName,
+			Namespace: owner.Namespace,
+		})
+		if err != nil {
+			return nil, err
+		}
+	}
+
+	return reports, nil
+}


Test patch:
diff --git a/pkg/kubebench/converter_test.go b/pkg/kubebench/converter_test.go
index 6055136..751b19c 100644
--- a/pkg/kubebench/converter_test.go
+++ b/pkg/kubebench/converter_test.go
@@ -3,20 +3,17 @@ package kubebench_test
 import (
 	"encoding/json"
 	"errors"
-	"github.com/aquasecurity/starboard/pkg/ext"
 	"os"
 	"testing"
 	"time"
 
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
+	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/ext"
 	"github.com/aquasecurity/starboard/pkg/kubebench"
-
 	"github.com/aquasecurity/starboard/pkg/starboard"
-
-	starboardv1alpha1 "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
 var (
@@ -76,14 +73,14 @@ func TestConverter_Convert(t *testing.T) {
 	}
 }
 
-func expectedOutputFrom(t *testing.T, fileName string) starboardv1alpha1.CISKubeBenchOutput {
+func expectedOutputFrom(t *testing.T, fileName string) v1alpha1.CISKubeBenchOutput {
 	t.Helper()
 
 	file, err := os.Open(fileName)
 	require.NoError(t, err)
 	defer file.Close()
 
-	var expectedOutput starboardv1alpha1.CISKubeBenchOutput
+	var expectedOutput v1alpha1.CISKubeBenchOutput
 	err = json.NewDecoder(file).Decode(&expectedOutput)
 	require.NoError(t, err)
 
diff --git a/pkg/vulnerabilityreport/io_test.go b/pkg/vulnerabilityreport/io_test.go
index 2470f7e..b9fdcce 100644
--- a/pkg/vulnerabilityreport/io_test.go
+++ b/pkg/vulnerabilityreport/io_test.go
@@ -5,206 +5,24 @@ import (
 	"testing"
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
-	fakeStarboard "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned/fake"
 	"github.com/aquasecurity/starboard/pkg/kube"
 	"github.com/aquasecurity/starboard/pkg/starboard"
 	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/labels"
 	"k8s.io/apimachinery/pkg/types"
 	fakeKubernetes "k8s.io/client-go/kubernetes/fake"
 	controllerFake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
-func TestReadWriter(t *testing.T) {
-
-	t.Run("Should create VulnerabilityReport", func(t *testing.T) {
-		clientset := fakeStarboard.NewSimpleClientset()
-		readWriter := vulnerabilityreport.NewReadWriter(clientset, fakeKubernetes.NewSimpleClientset())
-		err := readWriter.Write(context.TODO(), []v1alpha1.VulnerabilityReport{
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: "qa",
-					Name:      "deployment-app-c1",
-					Labels: map[string]string{
-						kube.LabelResourceKind:      "Deployment",
-						kube.LabelResourceName:      "app",
-						kube.LabelResourceNamespace: "qa",
-						kube.LabelContainerName:     "c1",
-						kube.LabelPodSpecHash:       "abc",
-					},
-				},
-			},
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: "qa",
-					Name:      "deployment-app-c2",
-					Labels: map[string]string{
-						kube.LabelResourceKind:      "Deployment",
-						kube.LabelResourceName:      "app",
-						kube.LabelResourceNamespace: "qa",
-						kube.LabelContainerName:     "c2",
-						kube.LabelPodSpecHash:       "def",
-					},
-				},
-			},
-		})
-		require.NoError(t, err)
-		list, err := clientset.AquasecurityV1alpha1().VulnerabilityReports("qa").
-			List(context.TODO(), metav1.ListOptions{LabelSelector: labels.Set{
-				kube.LabelResourceKind: "Deployment",
-				kube.LabelResourceName: "app"}.String(),
-			})
-		require.NoError(t, err)
-		reports := map[string]v1alpha1.VulnerabilityReport{}
-		for _, item := range list.Items {
-			reports[item.Name] = item
-		}
-		assert.Equal(t, map[string]v1alpha1.VulnerabilityReport{
-			"deployment-app-c1": {
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: "qa",
-					Name:      "deployment-app-c1",
-					Labels: map[string]string{
-						kube.LabelResourceKind:      "Deployment",
-						kube.LabelResourceName:      "app",
-						kube.LabelResourceNamespace: "qa",
-						kube.LabelContainerName:     "c1",
-						kube.LabelPodSpecHash:       "abc",
-					},
-				},
-			},
-			"deployment-app-c2": {
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: "qa",
-					Name:      "deployment-app-c2",
-					Labels: map[string]string{
-						kube.LabelResourceKind:      "Deployment",
-						kube.LabelResourceName:      "app",
-						kube.LabelResourceNamespace: "qa",
-						kube.LabelContainerName:     "c2",
-						kube.LabelPodSpecHash:       "def",
-					},
-				},
-			},
-		}, reports)
-	})
-
-	t.Run("Should update VulnerabilityReport", func(t *testing.T) {
-		clientset := fakeStarboard.NewSimpleClientset(&v1alpha1.VulnerabilityReport{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "qa",
-				Name:      "deployment-nginx",
-				Labels: map[string]string{
-					kube.LabelResourceKind:      "Deployment",
-					kube.LabelResourceName:      "nginx",
-					kube.LabelResourceNamespace: "qa",
-					kube.LabelContainerName:     "nginx",
-					kube.LabelPodSpecHash:       "abc",
-				},
-			},
-		})
-		readWriter := vulnerabilityreport.NewReadWriter(clientset, fakeKubernetes.NewSimpleClientset())
-		err := readWriter.Write(context.TODO(), []v1alpha1.VulnerabilityReport{
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Namespace: "qa",
-					Name:      "deployment-nginx",
-					Labels: map[string]string{
-						kube.LabelResourceKind:      "Deployment",
-						kube.LabelResourceName:      "nginx",
-						kube.LabelResourceNamespace: "qa",
-						kube.LabelContainerName:     "nginx",
-						kube.LabelPodSpecHash:       "xyz",
-					},
-				},
-			},
-		})
-		require.NoError(t, err)
-		report, err := clientset.AquasecurityV1alpha1().VulnerabilityReports("qa").Get(context.TODO(), "deployment-nginx", metav1.GetOptions{})
-		require.NoError(t, err)
-		assert.Equal(t, &v1alpha1.VulnerabilityReport{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "qa",
-				Name:      "deployment-nginx",
-				Labels: map[string]string{
-					kube.LabelResourceKind:      "Deployment",
-					kube.LabelResourceName:      "nginx",
-					kube.LabelResourceNamespace: "qa",
-					kube.LabelContainerName:     "nginx",
-					kube.LabelPodSpecHash:       "xyz",
-				},
-			},
-		}, report)
-	})
-
-	t.Run("Should find VulnerabilityReports", func(t *testing.T) {
-		clientset := fakeStarboard.NewSimpleClientset(&v1alpha1.VulnerabilityReport{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "my-namespace",
-				Name:      "deployment-my-deploy-my-container-01",
-				Labels: map[string]string{
-					kube.LabelResourceKind:      string(kube.KindDeployment),
-					kube.LabelResourceName:      "my-deploy",
-					kube.LabelResourceNamespace: "my-namespace",
-					kube.LabelContainerName:     "my-container-01",
-				},
-			},
-			Report: v1alpha1.VulnerabilityScanResult{},
-		}, &v1alpha1.VulnerabilityReport{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "my-namespace",
-				Name:      "deployment-my-deploy-my-container-02",
-				Labels: map[string]string{
-					kube.LabelResourceKind:      string(kube.KindDeployment),
-					kube.LabelResourceName:      "my-deploy",
-					kube.LabelResourceNamespace: "my-namespace",
-					kube.LabelContainerName:     "my-container-02",
-				},
-			},
-			Report: v1alpha1.VulnerabilityScanResult{},
-		}, &v1alpha1.VulnerabilityReport{
-			ObjectMeta: metav1.ObjectMeta{
-				Namespace: "my-namespace",
-				Name:      "my-sts",
-				Labels: map[string]string{
-					kube.LabelResourceKind:      string(kube.KindStatefulSet),
-					kube.LabelResourceName:      "my-sts",
-					kube.LabelResourceNamespace: "my-namespace",
-					kube.LabelContainerName:     "my-sts-container",
-				},
-			},
-			Report: v1alpha1.VulnerabilityScanResult{},
-		})
-
-		readWriter := vulnerabilityreport.NewReadWriter(clientset, fakeKubernetes.NewSimpleClientset())
-		list, err := readWriter.FindByOwner(context.TODO(), kube.Object{
-			Kind:      kube.KindDeployment,
-			Name:      "my-deploy",
-			Namespace: "my-namespace",
-		})
-		require.NoError(t, err)
-		reports := map[string]bool{}
-		for _, item := range list {
-			reports[item.Name] = true
-		}
-		assert.Equal(t, map[string]bool{
-			"deployment-my-deploy-my-container-01": true,
-			"deployment-my-deploy-my-container-02": true,
-		}, reports)
-	})
-
-}
-
 func TestControllerRuntimeReadWriter(t *testing.T) {
 
 	kubernetesScheme := starboard.NewScheme()
 
 	t.Run("Should create VulnerabilityReports", func(t *testing.T) {
 		client := controllerFake.NewClientBuilder().WithScheme(kubernetesScheme).Build()
-		readWriter := vulnerabilityreport.NewControllerRuntimeReadWriter(client)
+		readWriter := vulnerabilityreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
 		err := readWriter.Write(context.TODO(), []v1alpha1.VulnerabilityReport{
 			{
 				ObjectMeta: metav1.ObjectMeta{
@@ -301,7 +119,7 @@ func TestControllerRuntimeReadWriter(t *testing.T) {
 					},
 				},
 			}).Build()
-		readWriter := vulnerabilityreport.NewControllerRuntimeReadWriter(client)
+		readWriter := vulnerabilityreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
 		err := readWriter.Write(context.TODO(), []v1alpha1.VulnerabilityReport{
 			{
 				ObjectMeta: metav1.ObjectMeta{
@@ -421,7 +239,7 @@ func TestControllerRuntimeReadWriter(t *testing.T) {
 			Report: v1alpha1.VulnerabilityScanResult{},
 		}).Build()
 
-		readWriter := vulnerabilityreport.NewControllerRuntimeReadWriter(client)
+		readWriter := vulnerabilityreport.NewReadWriter(client, fakeKubernetes.NewSimpleClientset())
 		list, err := readWriter.FindByOwner(context.TODO(), kube.Object{
 			Kind:      kube.KindDeployment,
 			Name:      "my-deploy",