Repository: aquasecurity/starboard
Pull request #321 (2021-01-08 20:34:44)

refactor: Delete StoreInterface in favour of ReadWriter
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/321?src=pr&el=h1) Report
> Merging [#321](https://codecov.io/gh/aquasecurity/starboard/pull/321?src=pr&el=desc) (a12b3c6) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/b73bc8cb9e061f741904d63895c3b9827843b67a?el=desc) (b73bc8c) will **decrease** coverage by `35.67%`.
> The diff coverage is `54.83%`.

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

```diff
@@             Coverage Diff             @@
##             main     #321       +/-   ##
===========================================
- Coverage   62.18%   26.50%   -35.68%     
===========================================
  Files          57       50        -7     
  Lines        2774     2456      -318     
===========================================
- Hits         1725      651     -1074     
- Misses        823     1753      +930     
+ Partials      226       52      -174     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/321?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/operator/controller/analyzer.go](https://codecov.io/gh/aquasecurity/starboard/pull/321/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvYW5hbHl6ZXIuZ28=) | `0.00% <ø> (-70.28%)` | :arrow_down: |
| [pkg/operator/controller/reconciler.go](https://codecov.io/gh/aquasecurity/starboard/pull/321/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvcmVjb25jaWxlci5nbw==) | `0.00% <0.00%> (-58.47%)` | :arrow_down: |
| [pkg/starboard/config.go](https://codecov.io/gh/aquasecurity/starboard/pull/321/diff?src=pr&el=tree#diff-cGtnL3N0YXJib2FyZC9jb25maWcuZ28=) | `69.29% <0.00%> (+0.60%)` | :arrow_up: |
| [pkg/vulnerabilityreport/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/321/diff?src=pr&el=tree#diff-cGtnL3Z1bG5lcmFiaWxpdHlyZXBvcnQvc2Nhbm5lci5nbw==) | `0.00% <ø> (-73.47%)` | :arrow_down: |
| [pkg/vulnerabilityreport/writer.go](https://codecov.io/gh/aquasecurity/starboard/pull/321/diff?src=pr&el=tree#diff-cGtnL3Z1bG5lcmFiaWxpdHlyZXBvcnQvd3JpdGVyLmdv) | `72.13% <58.62%> (-12.25%)` | :arrow_down: |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/321/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/321/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/321/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/321/diff?src=pr&el=tree#diff-cGtnL2NtZC9zY2FuLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/polaris.go](https://codecov.io/gh/aquasecurity/starboard/pull/321/diff?src=pr&el=tree#diff-cGtnL2NtZC9wb2xhcmlzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [33 more](https://codecov.io/gh/aquasecurity/starboard/pull/321/diff?src=pr&el=tree-more) | |

------

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


Patch:
diff --git a/pkg/operator/controller/analyzer.go b/pkg/operator/controller/analyzer.go
index a4332a9..3dbc177 100644
--- a/pkg/operator/controller/analyzer.go
+++ b/pkg/operator/controller/analyzer.go
@@ -18,7 +18,7 @@ type Analyzer interface {
 	IsConcurrentScanJobsLimitExceeded(ctx context.Context) (bool, int, error)
 }
 
-func NewAnalyzer(config etc.Operator, store vulnerabilityreport.StoreInterface, client client.Client) Analyzer {
+func NewAnalyzer(config etc.Operator, store vulnerabilityreport.ReadWriter, client client.Client) Analyzer {
 	return &analyzer{
 		config: config,
 		store:  store,
@@ -29,7 +29,7 @@ func NewAnalyzer(config etc.Operator, store vulnerabilityreport.StoreInterface,
 type analyzer struct {
 	config etc.Operator
 	client client.Client
-	store  vulnerabilityreport.StoreInterface
+	store  vulnerabilityreport.ReadWriter
 }
 
 func (a *analyzer) HasVulnerabilityReports(ctx context.Context, owner kube.Object, images kube.ContainerImages, hash string) (bool, error) {
diff --git a/pkg/operator/controller/reconciler.go b/pkg/operator/controller/reconciler.go
index 1454124..accacb0 100644
--- a/pkg/operator/controller/reconciler.go
+++ b/pkg/operator/controller/reconciler.go
@@ -34,7 +34,7 @@ type Reconciler interface {
 func NewReconciler(scheme *runtime.Scheme,
 	config etc.Operator,
 	client client.Client,
-	store vulnerabilityreport.StoreInterface,
+	store vulnerabilityreport.ReadWriter,
 	idGenerator ext.IDGenerator,
 	scanner vulnerabilityreport.Plugin,
 	logsReader *logs.Reader,
@@ -55,7 +55,7 @@ type reconciler struct {
 	scheme      *runtime.Scheme
 	config      etc.Operator
 	client      client.Client
-	store       vulnerabilityreport.StoreInterface
+	store       vulnerabilityreport.ReadWriter
 	idGenerator ext.IDGenerator
 	scanner     vulnerabilityreport.Plugin
 	logsReader  *logs.Reader
@@ -188,7 +188,7 @@ func (r *reconciler) ParseLogsAndSaveVulnerabilityReports(ctx context.Context, s
 		vulnerabilityReports = append(vulnerabilityReports, report)
 	}
 
-	return r.store.Save(ctx, vulnerabilityReports)
+	return r.store.Write(ctx, vulnerabilityReports)
 }
 
 // TODO Add to utilities used both by CLI and Operator
diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go
index 2d3c424..417abf3 100644
--- a/pkg/operator/operator.go
+++ b/pkg/operator/operator.go
@@ -116,7 +116,7 @@ func Run(buildInfo starboard.BuildInfo, operatorConfig etc.Config) error {
 		return err
 	}
 
-	store := vulnerabilityreport.NewStore(mgr.GetClient(), mgr.GetScheme())
+	store := vulnerabilityreport.NewControllerRuntimeReadWriter(mgr.GetClient(), mgr.GetScheme())
 	idGenerator := ext.NewGoogleUUIDGenerator()
 
 	scanner, err := config.GetVulnerabilityReportPlugin(buildInfo, starboardConfig)
diff --git a/pkg/starboard/config.go b/pkg/starboard/config.go
index fc2c92e..b508873 100644
--- a/pkg/starboard/config.go
+++ b/pkg/starboard/config.go
@@ -361,7 +361,7 @@ func GetVersionFromImageRef(imageRef string) (string, error) {
 }
 
 // NewConfigManager constructs a new ConfigManager that is using kubernetes.Interface
-// to manage ConfigData backed by the the ConfigMap stored in the specified namespace.
+// to manage ConfigData backed by the ConfigMap stored in the specified namespace.
 func NewConfigManager(client kubernetes.Interface, namespace string) ConfigManager {
 	return &configManager{
 		client:    client,
@@ -441,9 +441,9 @@ func (c *configManager) Delete(ctx context.Context) error {
 	return nil
 }
 
-// LinuxNodeAffinity constructs a new Affinaty resource with linux supported nodes
+// LinuxNodeAffinity constructs a new Affinity resource with linux supported nodes.
 func LinuxNodeAffinity() *corev1.Affinity {
-	affinity := &corev1.Affinity{
+	return &corev1.Affinity{
 		NodeAffinity: &corev1.NodeAffinity{
 			RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{
 				NodeSelectorTerms: []corev1.NodeSelectorTerm{
@@ -457,6 +457,4 @@ func LinuxNodeAffinity() *corev1.Affinity {
 						},
 					},
 				}}}}
-
-	return affinity
 }
diff --git a/pkg/vulnerabilityreport/doc.go b/pkg/vulnerabilityreport/doc.go
new file mode 100644
index 0000000..39c31ff
--- /dev/null
+++ b/pkg/vulnerabilityreport/doc.go
@@ -0,0 +1,3 @@
+// Package vulnerabilityreport provides primitives for working with vulnerability
+// scanners.
+package vulnerabilityreport
diff --git a/pkg/vulnerabilityreport/scanner.go b/pkg/vulnerabilityreport/scanner.go
index 3099112..93811d0 100644
--- a/pkg/vulnerabilityreport/scanner.go
+++ b/pkg/vulnerabilityreport/scanner.go
@@ -24,7 +24,7 @@ import (
 )
 
 // Scanner is a template for running static vulnerability scanners that implement
-// the vulnerabilityreport.Plugin interface.
+// the Plugin interface.
 type Scanner struct {
 	scheme    *runtime.Scheme
 	clientset kubernetes.Interface
@@ -36,7 +36,7 @@ type Scanner struct {
 }
 
 // NewScanner constructs a new static vulnerability Scanner with the specified
-// vulnerabilityreport.Plugin that knows how to perform the actual scanning,
+// Plugin that knows how to perform the actual scanning,
 // which is performed by running a Kubernetes job, and knows how to convert logs
 // to instances of v1alpha1.VulnerabilityReport.
 func NewScanner(
@@ -57,11 +57,11 @@ func NewScanner(
 }
 
 // Scan creates a Kubernetes job to scan the specified workload. The pod created
-// by the scan job has template contributed by the vulnerabilityreport.Plugin.
+// by the scan job has template contributed by the Plugin.
 // It is a blocking method that watches the status of the job until it succeeds
 // or fails. When succeeded it parses container logs and coverts the output
 // to instances of v1alpha1.VulnerabilityReport by delegating such transformation
-// logic also to the vulnerabilityreport.Plugin.
+// logic also to the Plugin.
 func (s *Scanner) Scan(ctx context.Context, workload kube.Object) ([]v1alpha1.VulnerabilityReport, error) {
 	klog.V(3).Infof("Getting Pod template for workload: %v", workload)
 	spec, owner, err := s.pods.GetPodSpecByWorkload(ctx, workload)
diff --git a/pkg/vulnerabilityreport/store.go b/pkg/vulnerabilityreport/store.go
deleted file mode 100644
index 853c6c5..0000000
--- a/pkg/vulnerabilityreport/store.go
+++ /dev/null
@@ -1,77 +0,0 @@
-package vulnerabilityreport
-
-import (
-	"context"
-
-	"k8s.io/apimachinery/pkg/api/errors"
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/apimachinery/pkg/types"
-
-	starboardv1alpha1 "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
-	"github.com/aquasecurity/starboard/pkg/kube"
-	"sigs.k8s.io/controller-runtime/pkg/client"
-)
-
-type StoreInterface interface {
-	Save(context.Context, []starboardv1alpha1.VulnerabilityReport) error
-	FindByOwner(context.Context, kube.Object) ([]starboardv1alpha1.VulnerabilityReport, error)
-}
-
-type store struct {
-	client client.Client
-	scheme *runtime.Scheme
-}
-
-func NewStore(client client.Client, scheme *runtime.Scheme) StoreInterface {
-	return &store{
-		client: client,
-		scheme: scheme,
-	}
-}
-
-func (s *store) Save(ctx context.Context, reports []starboardv1alpha1.VulnerabilityReport) error {
-	for _, report := range reports {
-		err := s.createOrUpdate(ctx, report)
-		if err != nil {
-			return err
-		}
-	}
-	return nil
-}
-
-func (s *store) createOrUpdate(ctx context.Context, report starboardv1alpha1.VulnerabilityReport) error {
-	var existing starboardv1alpha1.VulnerabilityReport
-	err := s.client.Get(ctx, types.NamespacedName{
-		Name:      report.Name,
-		Namespace: report.Namespace,
-	}, &existing)
-
-	if err == nil {
-		copied := existing.DeepCopy()
-		copied.Labels = report.Labels
-		copied.Report = report.Report
-
-		return s.client.Update(ctx, copied)
-	}
-
-	if errors.IsNotFound(err) {
-		return s.client.Create(ctx, &report)
-	}
-
-	return err
-}
-
-func (s *store) FindByOwner(ctx context.Context, owner kube.Object) ([]starboardv1alpha1.VulnerabilityReport, error) {
-	var list starboardv1alpha1.VulnerabilityReportList
-
-	err := s.client.List(ctx, &list, client.MatchingLabels{
-		kube.LabelResourceKind:      string(owner.Kind),
-		kube.LabelResourceNamespace: owner.Namespace,
-		kube.LabelResourceName:      owner.Name,
-	}, client.InNamespace(owner.Namespace))
-	if err != nil {
-		return nil, err
-	}
-
-	return list.DeepCopy().Items, nil
-}
diff --git a/pkg/vulnerabilityreport/writer.go b/pkg/vulnerabilityreport/writer.go
index 32e3926..2481331 100644
--- a/pkg/vulnerabilityreport/writer.go
+++ b/pkg/vulnerabilityreport/writer.go
@@ -3,28 +3,29 @@ package vulnerabilityreport
 import (
 	"context"
 
-	starboard "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
+	"github.com/aquasecurity/starboard/pkg/kube"
 	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/klog"
-
-	clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	"github.com/aquasecurity/starboard/pkg/kube"
+	"sigs.k8s.io/controller-runtime/pkg/client"
 )
 
 // WorkloadVulnerabilities holds VulnerabilityReports for each container
 // of a Kubernetes workload.
 // Deprecated use a slice of VulnerabilityReports instead
-type WorkloadVulnerabilities map[string]starboard.VulnerabilityScanResult
+type WorkloadVulnerabilities map[string]v1alpha1.VulnerabilityScanResult
 
 type Writer interface {
-	Write(context.Context, []starboard.VulnerabilityReport) error
+	Write(context.Context, []v1alpha1.VulnerabilityReport) error
 }
 
 type Reader interface {
-	FindByOwner(context.Context, kube.Object) ([]starboard.VulnerabilityReport, error)
+	FindByOwner(context.Context, kube.Object) ([]v1alpha1.VulnerabilityReport, error)
 }
 
 type ReadWriter interface {
@@ -36,13 +37,15 @@ type readWriter struct {
 	client clientset.Interface
 }
 
+// NewReadWriter constructs a new ReadWriter which is using the client-go
+// module for interacting with the Kubernetes API server.
 func NewReadWriter(client clientset.Interface) ReadWriter {
 	return &readWriter{
 		client: client,
 	}
 }
 
-func (s *readWriter) Write(ctx context.Context, reports []starboard.VulnerabilityReport) error {
+func (s *readWriter) Write(ctx context.Context, reports []v1alpha1.VulnerabilityReport) error {
 	for _, report := range reports {
 		err := s.createOrUpdate(ctx, report)
 		if err != nil {
@@ -52,7 +55,7 @@ func (s *readWriter) Write(ctx context.Context, reports []starboard.Vulnerabilit
 	return nil
 }
 
-func (s *readWriter) createOrUpdate(ctx context.Context, report starboard.VulnerabilityReport) error {
+func (s *readWriter) createOrUpdate(ctx context.Context, report v1alpha1.VulnerabilityReport) error {
 	existing, err := s.client.AquasecurityV1alpha1().VulnerabilityReports(report.Namespace).
 		Get(ctx, report.Name, metav1.GetOptions{})
 
@@ -77,7 +80,7 @@ func (s *readWriter) createOrUpdate(ctx context.Context, report starboard.Vulner
 	return err
 }
 
-func (s *readWriter) FindByOwner(ctx context.Context, owner kube.Object) ([]starboard.VulnerabilityReport, error) {
+func (s *readWriter) FindByOwner(ctx context.Context, owner kube.Object) ([]v1alpha1.VulnerabilityReport, error) {
 	list, err := s.client.AquasecurityV1alpha1().VulnerabilityReports(owner.Namespace).List(ctx, metav1.ListOptions{
 		LabelSelector: labels.Set{
 			kube.LabelResourceKind:      string(owner.Kind),
@@ -90,3 +93,65 @@ func (s *readWriter) FindByOwner(ctx context.Context, owner kube.Object) ([]star
 	}
 	return list.DeepCopy().Items, nil
 }
+
+type crReadWriter struct {
+	client client.Client
+	scheme *runtime.Scheme
+}
+
+// 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, scheme *runtime.Scheme) ReadWriter {
+	return &crReadWriter{
+		client: client,
+		scheme: scheme,
+	}
+}
+
+func (s *crReadWriter) Write(ctx context.Context, reports []v1alpha1.VulnerabilityReport) error {
+	for _, report := range reports {
+		err := s.createOrUpdate(ctx, report)
+		if err != nil {
+			return err
+		}
+	}
+	return nil
+}
+
+func (s *crReadWriter) createOrUpdate(ctx context.Context, report v1alpha1.VulnerabilityReport) error {
+	var existing v1alpha1.VulnerabilityReport
+	err := s.client.Get(ctx, types.NamespacedName{
+		Name:      report.Name,
+		Namespace: report.Namespace,
+	}, &existing)
+
+	if err == nil {
+		copied := existing.DeepCopy()
+		copied.Labels = report.Labels
+		copied.Report = report.Report
+
+		return s.client.Update(ctx, copied)
+	}
+
+	if errors.IsNotFound(err) {
+		return s.client.Create(ctx, &report)
+	}
+
+	return err
+}
+
+func (s *crReadWriter) FindByOwner(ctx context.Context, owner kube.Object) ([]v1alpha1.VulnerabilityReport, error) {
+	var list v1alpha1.VulnerabilityReportList
+
+	err := s.client.List(ctx, &list, client.MatchingLabels{
+		kube.LabelResourceKind:      string(owner.Kind),
+		kube.LabelResourceNamespace: owner.Namespace,
+		kube.LabelResourceName:      owner.Name,
+	}, client.InNamespace(owner.Namespace))
+	if err != nil {
+		return nil, err
+	}
+
+	return list.DeepCopy().Items, nil
+}


Test patch:
diff --git a/pkg/vulnerabilityreport/store_test.go b/pkg/vulnerabilityreport/store_test.go
deleted file mode 100644
index 22a2a36..0000000
--- a/pkg/vulnerabilityreport/store_test.go
+++ /dev/null
@@ -1,167 +0,0 @@
-package vulnerabilityreport_test
-
-import (
-	"context"
-	"testing"
-
-	"github.com/aquasecurity/starboard/pkg/starboard"
-
-	"github.com/stretchr/testify/assert"
-	"k8s.io/apimachinery/pkg/types"
-
-	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
-	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
-	"github.com/stretchr/testify/require"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"sigs.k8s.io/controller-runtime/pkg/client/fake"
-)
-
-func TestStore_Save(t *testing.T) {
-
-	kubernetesScheme := starboard.NewScheme()
-
-	t.Run("Should create VulnerabilityReports", func(t *testing.T) {
-
-		client := fake.NewFakeClientWithScheme(kubernetesScheme)
-		store := vulnerabilityreport.NewStore(client, kubernetesScheme)
-		err := store.Save(context.TODO(), []v1alpha1.VulnerabilityReport{
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "deployment-app1-container1",
-					Namespace: "qa",
-					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container1",
-						"pod-spec-hash":                "h1",
-					},
-				},
-			},
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "deployment-app1-container2",
-					Namespace: "qa",
-					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container2",
-						"pod-spec-hash":                "h1",
-					},
-				},
-			},
-		})
-		require.NoError(t, err)
-
-	})
-
-	t.Run("Should update VulnerabilityReports", func(t *testing.T) {
-		client := fake.NewFakeClientWithScheme(kubernetesScheme,
-			&v1alpha1.VulnerabilityReport{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "deployment-app1-container1",
-					Namespace: "qa",
-					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container1",
-						"pod-spec-hash":                "h1",
-					},
-				},
-			},
-			&v1alpha1.VulnerabilityReport{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "deployment-app1-container2",
-					Namespace: "qa",
-					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container2",
-						"pod-spec-hash":                "h2",
-					},
-				},
-			})
-		store := vulnerabilityreport.NewStore(client, kubernetesScheme)
-		err := store.Save(context.TODO(), []v1alpha1.VulnerabilityReport{
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "deployment-app1-container1",
-					Namespace: "qa",
-					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container1",
-						"pod-spec-hash":                "h2",
-					},
-				},
-			},
-			{
-				ObjectMeta: metav1.ObjectMeta{
-					Name:      "deployment-app1-container2",
-					Namespace: "qa",
-					Labels: map[string]string{
-						"starboard.resource.kind":      "Deployment",
-						"starboard.resource.name":      "app1",
-						"starboard.resource.namespace": "qa",
-						"starboard.container.name":     "container2",
-						"pod-spec-hash":                "h2",
-					},
-				},
-			},
-		})
-		require.NoError(t, err)
-
-		var found v1alpha1.VulnerabilityReport
-		err = client.Get(context.TODO(), types.NamespacedName{
-			Namespace: "qa",
-			Name:      "deployment-app1-container1",
-		}, &found)
-		require.NoError(t, err)
-		assert.Equal(t, v1alpha1.VulnerabilityReport{
-			TypeMeta: metav1.TypeMeta{
-				APIVersion: "aquasecurity.github.io/v1alpha1",
-				Kind:       "VulnerabilityReport",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				ResourceVersion: "1",
-				Name:            "deployment-app1-container1",
-				Namespace:       "qa",
-				Labels: map[string]string{
-					"starboard.resource.kind":      "Deployment",
-					"starboard.resource.name":      "app1",
-					"starboard.resource.namespace": "qa",
-					"starboard.container.name":     "container1",
-					"pod-spec-hash":                "h2",
-				},
-			},
-		}, found)
-
-		err = client.Get(context.TODO(), types.NamespacedName{
-			Namespace: "qa",
-			Name:      "deployment-app1-container2",
-		}, &found)
-		require.NoError(t, err)
-		assert.Equal(t, v1alpha1.VulnerabilityReport{
-			TypeMeta: metav1.TypeMeta{
-				APIVersion: "aquasecurity.github.io/v1alpha1",
-				Kind:       "VulnerabilityReport",
-			},
-			ObjectMeta: metav1.ObjectMeta{
-				ResourceVersion: "1",
-				Name:            "deployment-app1-container2",
-				Namespace:       "qa",
-				Labels: map[string]string{
-					"starboard.resource.kind":      "Deployment",
-					"starboard.resource.name":      "app1",
-					"starboard.resource.namespace": "qa",
-					"starboard.container.name":     "container2",
-					"pod-spec-hash":                "h2",
-				},
-			},
-		}, found)
-	})
-}
diff --git a/pkg/vulnerabilityreport/writer_test.go b/pkg/vulnerabilityreport/writer_test.go
index 2742d61..d8dd0e6 100644
--- a/pkg/vulnerabilityreport/writer_test.go
+++ b/pkg/vulnerabilityreport/writer_test.go
@@ -4,19 +4,20 @@ import (
 	"context"
 	"testing"
 
-	"github.com/stretchr/testify/assert"
-	"k8s.io/apimachinery/pkg/labels"
-
-	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
-
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"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"
+	controllerFake "sigs.k8s.io/controller-runtime/pkg/client/fake"
 )
 
-func TestReadWriter_Read(t *testing.T) {
+func TestReadWriter(t *testing.T) {
 
 	t.Run("Should create VulnerabilityReport", func(t *testing.T) {
 		clientset := fake.NewSimpleClientset()
@@ -163,3 +164,153 @@ func TestReadWriter_Read(t *testing.T) {
 	})
 
 }
+
+func TestControllerRuntimeReadWriter(t *testing.T) {
+
+	kubernetesScheme := starboard.NewScheme()
+
+	t.Run("Should create VulnerabilityReports", func(t *testing.T) {
+
+		client := controllerFake.NewFakeClientWithScheme(kubernetesScheme)
+		store := vulnerabilityreport.NewControllerRuntimeReadWriter(client, kubernetesScheme)
+		err := store.Write(context.TODO(), []v1alpha1.VulnerabilityReport{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "deployment-app1-container1",
+					Namespace: "qa",
+					Labels: map[string]string{
+						"starboard.resource.kind":      "Deployment",
+						"starboard.resource.name":      "app1",
+						"starboard.resource.namespace": "qa",
+						"starboard.container.name":     "container1",
+						"pod-spec-hash":                "h1",
+					},
+				},
+			},
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "deployment-app1-container2",
+					Namespace: "qa",
+					Labels: map[string]string{
+						"starboard.resource.kind":      "Deployment",
+						"starboard.resource.name":      "app1",
+						"starboard.resource.namespace": "qa",
+						"starboard.container.name":     "container2",
+						"pod-spec-hash":                "h1",
+					},
+				},
+			},
+		})
+		require.NoError(t, err)
+
+	})
+
+	t.Run("Should update VulnerabilityReports", func(t *testing.T) {
+		client := controllerFake.NewFakeClientWithScheme(kubernetesScheme,
+			&v1alpha1.VulnerabilityReport{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "deployment-app1-container1",
+					Namespace: "qa",
+					Labels: map[string]string{
+						"starboard.resource.kind":      "Deployment",
+						"starboard.resource.name":      "app1",
+						"starboard.resource.namespace": "qa",
+						"starboard.container.name":     "container1",
+						"pod-spec-hash":                "h1",
+					},
+				},
+			},
+			&v1alpha1.VulnerabilityReport{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "deployment-app1-container2",
+					Namespace: "qa",
+					Labels: map[string]string{
+						"starboard.resource.kind":      "Deployment",
+						"starboard.resource.name":      "app1",
+						"starboard.resource.namespace": "qa",
+						"starboard.container.name":     "container2",
+						"pod-spec-hash":                "h2",
+					},
+				},
+			})
+		store := vulnerabilityreport.NewControllerRuntimeReadWriter(client, kubernetesScheme)
+		err := store.Write(context.TODO(), []v1alpha1.VulnerabilityReport{
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "deployment-app1-container1",
+					Namespace: "qa",
+					Labels: map[string]string{
+						"starboard.resource.kind":      "Deployment",
+						"starboard.resource.name":      "app1",
+						"starboard.resource.namespace": "qa",
+						"starboard.container.name":     "container1",
+						"pod-spec-hash":                "h2",
+					},
+				},
+			},
+			{
+				ObjectMeta: metav1.ObjectMeta{
+					Name:      "deployment-app1-container2",
+					Namespace: "qa",
+					Labels: map[string]string{
+						"starboard.resource.kind":      "Deployment",
+						"starboard.resource.name":      "app1",
+						"starboard.resource.namespace": "qa",
+						"starboard.container.name":     "container2",
+						"pod-spec-hash":                "h2",
+					},
+				},
+			},
+		})
+		require.NoError(t, err)
+
+		var found v1alpha1.VulnerabilityReport
+		err = client.Get(context.TODO(), types.NamespacedName{
+			Namespace: "qa",
+			Name:      "deployment-app1-container1",
+		}, &found)
+		require.NoError(t, err)
+		assert.Equal(t, v1alpha1.VulnerabilityReport{
+			TypeMeta: metav1.TypeMeta{
+				APIVersion: "aquasecurity.github.io/v1alpha1",
+				Kind:       "VulnerabilityReport",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				ResourceVersion: "1",
+				Name:            "deployment-app1-container1",
+				Namespace:       "qa",
+				Labels: map[string]string{
+					"starboard.resource.kind":      "Deployment",
+					"starboard.resource.name":      "app1",
+					"starboard.resource.namespace": "qa",
+					"starboard.container.name":     "container1",
+					"pod-spec-hash":                "h2",
+				},
+			},
+		}, found)
+
+		err = client.Get(context.TODO(), types.NamespacedName{
+			Namespace: "qa",
+			Name:      "deployment-app1-container2",
+		}, &found)
+		require.NoError(t, err)
+		assert.Equal(t, v1alpha1.VulnerabilityReport{
+			TypeMeta: metav1.TypeMeta{
+				APIVersion: "aquasecurity.github.io/v1alpha1",
+				Kind:       "VulnerabilityReport",
+			},
+			ObjectMeta: metav1.ObjectMeta{
+				ResourceVersion: "1",
+				Name:            "deployment-app1-container2",
+				Namespace:       "qa",
+				Labels: map[string]string{
+					"starboard.resource.kind":      "Deployment",
+					"starboard.resource.name":      "app1",
+					"starboard.resource.namespace": "qa",
+					"starboard.container.name":     "container2",
+					"pod-spec-hash":                "h2",
+				},
+			},
+		}, found)
+	})
+}