Repository: aquasecurity/starboard
Pull request #241 (2020-11-16 08:33:25)

feat: Use deterministic names for ConfigAuditReports
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/241?src=pr&el=h1) Report
> Merging [#241](https://codecov.io/gh/aquasecurity/starboard/pull/241?src=pr&el=desc) (e2084f4) into [master](https://codecov.io/gh/aquasecurity/starboard/commit/beeb676beab733eeade801dcb3ecb23437185359?el=desc) (beeb676) will **decrease** coverage by `43.93%`.
> The diff coverage is `0.00%`.

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

```diff
@@             Coverage Diff             @@
##           master     #241       +/-   ##
===========================================
- Coverage   60.61%   16.68%   -43.94%     
===========================================
  Files          50       44        -6     
  Lines        2420     2074      -346     
===========================================
- Hits         1467      346     -1121     
- Misses        755     1696      +941     
+ Partials      198       32      -166     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/241?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/cmd/find\_vulnerabilities.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL2NtZC9maW5kX3Z1bG5lcmFiaWxpdGllcy5nbw==) | `0.00% <0.00%> (-68.97%)` | :arrow_down: |
| [pkg/cmd/get\_configaudit.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXRfY29uZmlnYXVkaXQuZ28=) | `0.00% <0.00%> (-30.62%)` | :arrow_down: |
| [pkg/cmd/get\_report.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXRfcmVwb3J0Lmdv) | `0.00% <0.00%> (-30.00%)` | :arrow_down: |
| [pkg/cmd/get\_vulnerabilities.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL2NtZC9nZXRfdnVsbmVyYWJpbGl0aWVzLmdv) | `0.00% <0.00%> (-62.00%)` | :arrow_down: |
| [pkg/cmd/polaris.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL2NtZC9wb2xhcmlzLmdv) | `0.00% <0.00%> (-48.58%)` | :arrow_down: |
| [pkg/configauditreport/builder.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL2NvbmZpZ2F1ZGl0cmVwb3J0L2J1aWxkZXIuZ28=) | `0.00% <0.00%> (ø)` | |
| [pkg/find/vulnerabilities/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL2ZpbmQvdnVsbmVyYWJpbGl0aWVzL3NjYW5uZXIuZ28=) | `0.00% <0.00%> (-74.26%)` | :arrow_down: |
| [pkg/operator/controller/reconciler.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvcmVjb25jaWxlci5nbw==) | `0.00% <0.00%> (-65.14%)` | :arrow_down: |
| [pkg/polaris/converter.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL3BvbGFyaXMvY29udmVydGVyLmdv) | `2.00% <0.00%> (-80.00%)` | :arrow_down: |
| [pkg/polaris/scanner.go](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree#diff-cGtnL3BvbGFyaXMvc2Nhbm5lci5nbw==) | `0.00% <0.00%> (-87.50%)` | :arrow_down: |
| ... and [39 more](https://codecov.io/gh/aquasecurity/starboard/pull/241/diff?src=pr&el=tree-more) | |

------

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


Patch:
diff --git a/Makefile b/Makefile
index 330649e..dfde4c2 100644
--- a/Makefile
+++ b/Makefile
@@ -65,7 +65,7 @@ itests-starboard: check-env get-ginkgo
 	github.com/aquasecurity/starboard/pkg/kubebench,\
 	github.com/aquasecurity/starboard/pkg/kubehunter,\
 	github.com/aquasecurity/starboard/pkg/polaris,\
-	github.com/aquasecurity/starboard/pkg/polaris/crd,\
+	github.com/aquasecurity/starboard/pkg/configauditreport,\
 	github.com/aquasecurity/starboard/pkg/trivy,\
 	github.com/aquasecurity/starboard/pkg/find/vulnerabilities,\
 	github.com/aquasecurity/starboard/pkg/vulnerabilityreport \
diff --git a/pkg/apis/aquasecurity/v1alpha1/config_audit_types.go b/pkg/apis/aquasecurity/v1alpha1/config_audit_types.go
index 87f2049..3c4ce8b 100644
--- a/pkg/apis/aquasecurity/v1alpha1/config_audit_types.go
+++ b/pkg/apis/aquasecurity/v1alpha1/config_audit_types.go
@@ -86,7 +86,7 @@ type ConfigAuditReport struct {
 	metav1.TypeMeta   `json:",inline"`
 	metav1.ObjectMeta `json:"metadata,omitempty"`
 
-	Report ConfigAudit `json:"report"`
+	Report ConfigAuditResult `json:"report"`
 }
 
 // +k8s:deepcopy-gen:interfaces=k8s.io/apimachinery/pkg/runtime.Object
@@ -99,7 +99,7 @@ type ConfigAuditReportList struct {
 	Items []ConfigAuditReport `json:"items"`
 }
 
-type ConfigAudit struct {
+type ConfigAuditResult struct {
 	UpdateTimestamp metav1.Time        `json:"updateTimestamp"`
 	Scanner         Scanner            `json:"scanner"`
 	Summary         ConfigAuditSummary `json:"summary"`
diff --git a/pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go b/pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go
index 45701c9..083c634 100644
--- a/pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go
+++ b/pkg/apis/aquasecurity/v1alpha1/zz_generated.deepcopy.go
@@ -202,45 +202,6 @@ func (in *Check) DeepCopy() *Check {
 	return out
 }
 
-// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
-func (in *ConfigAudit) DeepCopyInto(out *ConfigAudit) {
-	*out = *in
-	in.UpdateTimestamp.DeepCopyInto(&out.UpdateTimestamp)
-	out.Scanner = in.Scanner
-	out.Summary = in.Summary
-	if in.PodChecks != nil {
-		in, out := &in.PodChecks, &out.PodChecks
-		*out = make([]Check, len(*in))
-		copy(*out, *in)
-	}
-	if in.ContainerChecks != nil {
-		in, out := &in.ContainerChecks, &out.ContainerChecks
-		*out = make(map[string][]Check, len(*in))
-		for key, val := range *in {
-			var outVal []Check
-			if val == nil {
-				(*out)[key] = nil
-			} else {
-				in, out := &val, &outVal
-				*out = make([]Check, len(*in))
-				copy(*out, *in)
-			}
-			(*out)[key] = outVal
-		}
-	}
-	return
-}
-
-// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigAudit.
-func (in *ConfigAudit) DeepCopy() *ConfigAudit {
-	if in == nil {
-		return nil
-	}
-	out := new(ConfigAudit)
-	in.DeepCopyInto(out)
-	return out
-}
-
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ConfigAuditReport) DeepCopyInto(out *ConfigAuditReport) {
 	*out = *in
@@ -301,6 +262,45 @@ func (in *ConfigAuditReportList) DeepCopyObject() runtime.Object {
 	return nil
 }
 
+// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
+func (in *ConfigAuditResult) DeepCopyInto(out *ConfigAuditResult) {
+	*out = *in
+	in.UpdateTimestamp.DeepCopyInto(&out.UpdateTimestamp)
+	out.Scanner = in.Scanner
+	out.Summary = in.Summary
+	if in.PodChecks != nil {
+		in, out := &in.PodChecks, &out.PodChecks
+		*out = make([]Check, len(*in))
+		copy(*out, *in)
+	}
+	if in.ContainerChecks != nil {
+		in, out := &in.ContainerChecks, &out.ContainerChecks
+		*out = make(map[string][]Check, len(*in))
+		for key, val := range *in {
+			var outVal []Check
+			if val == nil {
+				(*out)[key] = nil
+			} else {
+				in, out := &val, &outVal
+				*out = make([]Check, len(*in))
+				copy(*out, *in)
+			}
+			(*out)[key] = outVal
+		}
+	}
+	return
+}
+
+// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new ConfigAuditResult.
+func (in *ConfigAuditResult) DeepCopy() *ConfigAuditResult {
+	if in == nil {
+		return nil
+	}
+	out := new(ConfigAuditResult)
+	in.DeepCopyInto(out)
+	return out
+}
+
 // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
 func (in *ConfigAuditSummary) DeepCopyInto(out *ConfigAuditSummary) {
 	*out = *in
diff --git a/pkg/cmd/find_vulnerabilities.go b/pkg/cmd/find_vulnerabilities.go
index efd0cbd..488ecb3 100644
--- a/pkg/cmd/find_vulnerabilities.go
+++ b/pkg/cmd/find_vulnerabilities.go
@@ -82,8 +82,7 @@ NAME is the name of a particular Kubernetes workload.
 			if err != nil {
 				return err
 			}
-			scheme := starboard.NewScheme()
-			reports, err := vulnerabilities.NewScanner(scheme, config, opts, kubernetesClientset).Scan(ctx, workload)
+			reports, err := vulnerabilities.NewScanner(starboard.NewScheme(), config, opts, kubernetesClientset).Scan(ctx, workload)
 			if err != nil {
 				return err
 			}
@@ -91,7 +90,7 @@ NAME is the name of a particular Kubernetes workload.
 			if err != nil {
 				return err
 			}
-			return vulnerabilityreport.NewReadWriter(scheme, starboardClientset).Write(ctx, reports)
+			return vulnerabilityreport.NewReadWriter(starboardClientset).Write(ctx, reports)
 		},
 	}
 
diff --git a/pkg/cmd/get_configaudit.go b/pkg/cmd/get_configaudit.go
index 68d1181..0e9f684 100644
--- a/pkg/cmd/get_configaudit.go
+++ b/pkg/cmd/get_configaudit.go
@@ -5,9 +5,9 @@ import (
 	"fmt"
 	"io"
 
-	"github.com/aquasecurity/starboard/pkg/starboard"
+	"github.com/aquasecurity/starboard/pkg/configauditreport"
 
-	"github.com/aquasecurity/starboard/pkg/polaris/crd"
+	"github.com/aquasecurity/starboard/pkg/starboard"
 
 	clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
 	"github.com/spf13/cobra"
@@ -58,8 +58,7 @@ NAME is the name of a particular Kubernetes workload.
 			if err != nil {
 				return err
 			}
-			scheme := starboard.NewScheme()
-			report, err := crd.NewReadWriter(scheme, client).FindByOwner(ctx, workload)
+			report, err := configauditreport.NewReadWriter(client).FindByOwner(ctx, workload)
 			if err != nil {
 				return nil
 			}
@@ -71,7 +70,7 @@ NAME is the name of a particular Kubernetes workload.
 
 			format := cmd.Flag("output").Value.String()
 			printer, err := genericclioptions.NewPrintFlags("").
-				WithTypeSetter(scheme).
+				WithTypeSetter(starboard.NewScheme()).
 				WithDefaultOutput(format).
 				ToPrinter()
 			if err != nil {
diff --git a/pkg/cmd/get_report.go b/pkg/cmd/get_report.go
index 12e74d0..70d7f61 100644
--- a/pkg/cmd/get_report.go
+++ b/pkg/cmd/get_report.go
@@ -4,12 +4,11 @@ import (
 	"fmt"
 	"os"
 
-	"github.com/aquasecurity/starboard/pkg/starboard"
+	"github.com/aquasecurity/starboard/pkg/configauditreport"
 
 	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
 
 	clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
-	configAuditCrd "github.com/aquasecurity/starboard/pkg/polaris/crd"
 	"github.com/aquasecurity/starboard/pkg/report"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
@@ -48,9 +47,8 @@ NAME is the name of a particular Kubernetes workload.
 				return err
 			}
 
-			scheme := starboard.NewScheme()
-			caReader := configAuditCrd.NewReadWriter(scheme, starboardClientset)
-			vulnsReader := vulnerabilityreport.NewReadWriter(scheme, starboardClientset)
+			caReader := configauditreport.NewReadWriter(starboardClientset)
+			vulnsReader := vulnerabilityreport.NewReadWriter(starboardClientset)
 
 			return report.NewHTMLReporter(caReader, vulnsReader).
 				GenerateReport(workload, os.Stdout)
diff --git a/pkg/cmd/get_vulnerabilities.go b/pkg/cmd/get_vulnerabilities.go
index a7f99e4..4b53cc8 100644
--- a/pkg/cmd/get_vulnerabilities.go
+++ b/pkg/cmd/get_vulnerabilities.go
@@ -61,8 +61,7 @@ NAME is the name of a particular Kubernetes workload.
 				return err
 			}
 
-			scheme := starboard.NewScheme()
-			items, err := vulnerabilityreport.NewReadWriter(scheme, client).FindByOwner(ctx, workload)
+			items, err := vulnerabilityreport.NewReadWriter(client).FindByOwner(ctx, workload)
 			if err != nil {
 				return fmt.Errorf("list vulnerability reports: %v", err)
 			}
@@ -73,7 +72,7 @@ NAME is the name of a particular Kubernetes workload.
 
 			format := cmd.Flag("output").Value.String()
 			printer, err := genericclioptions.NewPrintFlags("").
-				WithTypeSetter(scheme).
+				WithTypeSetter(starboard.NewScheme()).
 				WithDefaultOutput(format).
 				ToPrinter()
 			if err != nil {
diff --git a/pkg/cmd/polaris.go b/pkg/cmd/polaris.go
index 33fa04f..002896e 100644
--- a/pkg/cmd/polaris.go
+++ b/pkg/cmd/polaris.go
@@ -3,11 +3,11 @@ package cmd
 import (
 	"context"
 
+	"github.com/aquasecurity/starboard/pkg/configauditreport"
 	"github.com/aquasecurity/starboard/pkg/starboard"
 
 	starboardapi "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
 	"github.com/aquasecurity/starboard/pkg/polaris"
-	"github.com/aquasecurity/starboard/pkg/polaris/crd"
 	"github.com/spf13/cobra"
 	"k8s.io/cli-runtime/pkg/genericclioptions"
 	"k8s.io/client-go/kubernetes"
@@ -44,7 +44,7 @@ func NewPolarisCmd(cf *genericclioptions.ConfigFlags) *cobra.Command {
 			if err != nil {
 				return err
 			}
-			report, owner, err := polaris.NewScanner(opts, clientset).Scan(ctx, workload, gvk)
+			report, err := polaris.NewScanner(starboard.NewScheme(), opts, clientset).Scan(ctx, workload, gvk)
 			if err != nil {
 				return err
 			}
@@ -52,11 +52,7 @@ func NewPolarisCmd(cf *genericclioptions.ConfigFlags) *cobra.Command {
 			if err != nil {
 				return nil
 			}
-			err = crd.NewReadWriter(starboard.NewScheme(), starboardClientset).Write(ctx, report, owner)
-			if err != nil {
-				return err
-			}
-			return nil
+			return configauditreport.NewReadWriter(starboardClientset).Write(ctx, report)
 		},
 	}
 
diff --git a/pkg/configauditreport/builder.go b/pkg/configauditreport/builder.go
new file mode 100644
index 0000000..75dfbbd
--- /dev/null
+++ b/pkg/configauditreport/builder.go
@@ -0,0 +1,92 @@
+package configauditreport
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/kube"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
+)
+
+type Builder interface {
+	Owner(owner metav1.Object) Builder
+	PodSpecHash(hash string) Builder
+	Result(result v1alpha1.ConfigAuditResult) Builder
+	Get() (v1alpha1.ConfigAuditReport, error)
+}
+
+func NewBuilder(scheme *runtime.Scheme) Builder {
+	return &builder{
+		scheme: scheme,
+	}
+}
+
+type builder struct {
+	scheme *runtime.Scheme
+	owner  metav1.Object
+	hash   string
+	result v1alpha1.ConfigAuditResult
+}
+
+func (b *builder) Owner(owner metav1.Object) Builder {
+	b.owner = owner
+	return b
+}
+
+func (b *builder) PodSpecHash(hash string) Builder {
+	b.hash = hash
+	return b
+}
+
+func (b *builder) Result(result v1alpha1.ConfigAuditResult) Builder {
+	b.result = result
+	return b
+}
+
+func (b *builder) reportName() (string, error) {
+	kind, err := kube.KindForObject(b.owner, b.scheme)
+	if err != nil {
+		return "", err
+	}
+	return fmt.Sprintf("%s-%s", strings.ToLower(kind),
+		b.owner.GetName()), nil
+}
+
+func (b *builder) Get() (v1alpha1.ConfigAuditReport, error) {
+	kind, err := kube.KindForObject(b.owner, b.scheme)
+	if err != nil {
+		return v1alpha1.ConfigAuditReport{}, err
+	}
+
+	labels := map[string]string{
+		kube.LabelResourceKind:      kind,
+		kube.LabelResourceName:      b.owner.GetName(),
+		kube.LabelResourceNamespace: b.owner.GetNamespace(),
+	}
+
+	if b.hash != "" {
+		labels[kube.LabelPodSpecHash] = b.hash
+	}
+
+	reportName, err := b.reportName()
+	if err != nil {
+		return v1alpha1.ConfigAuditReport{}, err
+	}
+
+	report := v1alpha1.ConfigAuditReport{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      reportName,
+			Namespace: b.owner.GetNamespace(),
+			Labels:    labels,
+		},
+		Report: b.result,
+	}
+	err = controllerutil.SetOwnerReference(b.owner, &report, b.scheme)
+	if err != nil {
+		return v1alpha1.ConfigAuditReport{}, err
+	}
+	return report, nil
+}
diff --git a/pkg/configauditreport/writer.go b/pkg/configauditreport/writer.go
new file mode 100644
index 0000000..27ad529
--- /dev/null
+++ b/pkg/configauditreport/writer.go
@@ -0,0 +1,86 @@
+package configauditreport
+
+import (
+	"context"
+
+	clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
+	"k8s.io/apimachinery/pkg/api/errors"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/labels"
+	"k8s.io/klog"
+
+	starboard "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/kube"
+)
+
+// Write is the interface that wraps basic methods for persisting ConfigAudit reports.
+//
+// Write persists the given ConfigAuditReport report.
+type Writer interface {
+	Write(ctx context.Context, report starboard.ConfigAuditReport) error
+}
+
+// Reader is the interface that wraps basic methods for reading ConfigAudit reports.
+//
+// Read will return a single ConfigAuditReport that match a specific workload
+type Reader interface {
+	FindByOwner(ctx context.Context, owner kube.Object) (*starboard.ConfigAuditReport, error)
+}
+
+type ReadWriter interface {
+	Writer
+	Reader
+}
+
+type readWriter struct {
+	client clientset.Interface
+}
+
+func NewReadWriter(client clientset.Interface) ReadWriter {
+	return &readWriter{
+		client: client,
+	}
+}
+
+func (w *readWriter) Write(ctx context.Context, report starboard.ConfigAuditReport) error {
+	existing, err := w.client.AquasecurityV1alpha1().ConfigAuditReports(report.Namespace).
+		Get(ctx, report.Name, metav1.GetOptions{})
+
+	if err == nil {
+		klog.V(3).Infof("Updating ConfigAuditReport %q", report.Namespace+"/"+report.Name)
+		deepCopy := existing.DeepCopy()
+		deepCopy.Labels = report.Labels
+		deepCopy.Report = report.Report
+
+		_, err = w.client.AquasecurityV1alpha1().ConfigAuditReports(report.Namespace).
+			Update(ctx, deepCopy, metav1.UpdateOptions{})
+		return err
+	}
+
+	if errors.IsNotFound(err) {
+		klog.V(3).Infof("Creating ConfigAuditReport %q", report.Namespace+"/"+report.Name)
+		_, err = w.client.AquasecurityV1alpha1().ConfigAuditReports(report.Namespace).
+			Create(ctx, &report, metav1.CreateOptions{})
+		return err
+	}
+
+	return err
+}
+
+func (w *readWriter) FindByOwner(ctx context.Context, workload kube.Object) (*starboard.ConfigAuditReport, error) {
+	list, err := w.client.AquasecurityV1alpha1().ConfigAuditReports(workload.Namespace).List(ctx, metav1.ListOptions{
+		LabelSelector: labels.Set{
+			kube.LabelResourceKind:      string(workload.Kind),
+			kube.LabelResourceName:      workload.Name,
+			kube.LabelResourceNamespace: workload.Namespace,
+		}.String(),
+	})
+	if err != nil {
+		return nil, err
+	}
+	// Only one config audit per specific workload exists on the cluster
+	if len(list.Items) > 0 {
+		return &list.DeepCopy().Items[0], nil
+	}
+	return nil, nil
+}
diff --git a/pkg/find/vulnerabilities/scanner.go b/pkg/find/vulnerabilities/scanner.go
index bf49ae4..09ee757 100644
--- a/pkg/find/vulnerabilities/scanner.go
+++ b/pkg/find/vulnerabilities/scanner.go
@@ -178,7 +178,7 @@ func (s *Scanner) GetVulnerabilityReportsByScanJob(ctx context.Context, job *bat
 		report, err := vulnerabilityreport.NewBuilder(s.scheme).
 			Owner(owner).
 			Container(c.Name).
-			ScanResult(result).
+			Result(result).
 			PodSpecHash("").Get()
 		if err != nil {
 			return nil, err
diff --git a/pkg/operator/controller/reconciler.go b/pkg/operator/controller/reconciler.go
index 74b34f2..bd4062f 100644
--- a/pkg/operator/controller/reconciler.go
+++ b/pkg/operator/controller/reconciler.go
@@ -135,7 +135,7 @@ func (r *reconciler) ParseLogsAndSaveVulnerabilityReports(ctx context.Context, s
 		report, err := vulnerabilityreport.NewBuilder(r.scheme).
 			Owner(owner).
 			Container(container.Name).
-			ScanResult(scanResult).
+			Result(scanResult).
 			PodSpecHash(hash).Get()
 		if err != nil {
 			return err
diff --git a/pkg/polaris/converter.go b/pkg/polaris/converter.go
index 38c51e7..c219722 100644
--- a/pkg/polaris/converter.go
+++ b/pkg/polaris/converter.go
@@ -11,7 +11,7 @@ import (
 )
 
 type Converter interface {
-	Convert(reader io.Reader) (sec.ConfigAudit, error)
+	Convert(reader io.Reader) (sec.ConfigAuditResult, error)
 }
 
 type converter struct {
@@ -23,7 +23,7 @@ func NewConverter() Converter {
 	return &converter{}
 }
 
-func (c *converter) Convert(reader io.Reader) (reports sec.ConfigAudit, err error) {
+func (c *converter) Convert(reader io.Reader) (reports sec.ConfigAuditResult, err error) {
 	var report Report
 	err = json.NewDecoder(reader).Decode(&report)
 	if err != nil {
@@ -61,7 +61,7 @@ func (c *converter) toSummary(podChecks []sec.Check, containerChecks map[string]
 	return
 }
 
-func (c *converter) toConfigAudit(result Result) (report sec.ConfigAudit) {
+func (c *converter) toConfigAudit(result Result) (report sec.ConfigAuditResult) {
 	var podChecks []sec.Check
 	containerChecks := make(map[string][]sec.Check)
 
@@ -90,7 +90,7 @@ func (c *converter) toConfigAudit(result Result) (report sec.ConfigAudit) {
 		containerChecks[cr.Name] = checks
 	}
 
-	report = sec.ConfigAudit{
+	report = sec.ConfigAuditResult{
 		Scanner: sec.Scanner{
 			Name:    "Polaris",
 			Vendor:  "Fairwinds Ops",
diff --git a/pkg/polaris/crd/writer.go b/pkg/polaris/crd/writer.go
deleted file mode 100644
index 2a4a5b1..0000000
--- a/pkg/polaris/crd/writer.go
+++ /dev/null
@@ -1,93 +0,0 @@
-package crd
-
-import (
-	"context"
-	"fmt"
-	"strings"
-
-	"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
-
-	"k8s.io/apimachinery/pkg/runtime"
-
-	"k8s.io/apimachinery/pkg/labels"
-
-	starboard "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
-	clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
-	"github.com/aquasecurity/starboard/pkg/kube"
-	"github.com/aquasecurity/starboard/pkg/polaris"
-	"k8s.io/apimachinery/pkg/api/errors"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/klog"
-)
-
-type ReadWriter struct {
-	scheme *runtime.Scheme
-	client clientset.Interface
-}
-
-func NewReadWriter(scheme *runtime.Scheme, client clientset.Interface) polaris.ReadWriter {
-	return &ReadWriter{
-		scheme: scheme,
-		client: client,
-	}
-}
-
-func (w *ReadWriter) Write(ctx context.Context, report starboard.ConfigAudit, owner metav1.Object) (err error) {
-	var kind string
-	kind, err = kube.KindForObject(owner, w.scheme)
-	if err != nil {
-		return
-	}
-	namespace := owner.GetNamespace()
-	name := fmt.Sprintf("%s.%s", strings.ToLower(kind), owner.GetName())
-
-	existingCR, err := w.client.AquasecurityV1alpha1().ConfigAuditReports(namespace).Get(ctx, name, metav1.GetOptions{})
-
-	switch {
-	case err == nil:
-		klog.V(3).Infof("Updating config audit report: %s/%s", namespace, name)
-		deepCopy := existingCR.DeepCopy()
-		deepCopy.Report = report
-		_, err = w.client.AquasecurityV1alpha1().ConfigAuditReports(namespace).Update(ctx, deepCopy, metav1.UpdateOptions{})
-	case errors.IsNotFound(err):
-		klog.V(3).Infof("Creating config audit report: %s/%s", namespace, name)
-		report := &starboard.ConfigAuditReport{
-			ObjectMeta: metav1.ObjectMeta{
-				Name:      name,
-				Namespace: owner.GetNamespace(),
-				Labels: labels.Set{
-					kube.LabelResourceKind:      kind,
-					kube.LabelResourceName:      owner.GetName(),
-					kube.LabelResourceNamespace: owner.GetNamespace(),
-				},
-			},
-			Report: report,
-		}
-		err = controllerutil.SetOwnerReference(owner, report, w.scheme)
-		if err != nil {
-			return
-		}
-		_, err = w.client.AquasecurityV1alpha1().ConfigAuditReports(namespace).
-			Create(ctx, report, metav1.CreateOptions{})
-		return
-	}
-	return
-}
-
-func (w *ReadWriter) FindByOwner(ctx context.Context, workload kube.Object) (*starboard.ConfigAuditReport, error) {
-	list, err := w.client.AquasecurityV1alpha1().ConfigAuditReports(workload.Namespace).List(ctx, metav1.ListOptions{
-		LabelSelector: labels.Set{
-			kube.LabelResourceKind:      string(workload.Kind),
-			kube.LabelResourceName:      workload.Name,
-			kube.LabelResourceNamespace: workload.Namespace,
-		}.String(),
-	})
-	if err != nil {
-		return nil, err
-	}
-	// Only one config audit per specific workload exists on the cluster
-	if len(list.Items) > 0 {
-		return &list.DeepCopy().Items[0], nil
-	}
-	return nil, nil
-}
diff --git a/pkg/polaris/scanner.go b/pkg/polaris/scanner.go
index 49b8662..2b654de 100644
--- a/pkg/polaris/scanner.go
+++ b/pkg/polaris/scanner.go
@@ -4,6 +4,9 @@ import (
 	"context"
 	"fmt"
 
+	"github.com/aquasecurity/starboard/pkg/configauditreport"
+	"k8s.io/apimachinery/pkg/runtime"
+
 	"github.com/aquasecurity/starboard/pkg/starboard"
 
 	"k8s.io/apimachinery/pkg/runtime/schema"
@@ -37,14 +40,16 @@ var (
 )
 
 type Scanner struct {
+	scheme    *runtime.Scheme
 	opts      kube.ScannerOpts
 	clientset kubernetes.Interface
 	pods      *pod.Manager
 	converter Converter
 }
 
-func NewScanner(opts kube.ScannerOpts, clientset kubernetes.Interface) *Scanner {
+func NewScanner(scheme *runtime.Scheme, opts kube.ScannerOpts, clientset kubernetes.Interface) *Scanner {
 	return &Scanner{
+		scheme:    scheme,
 		opts:      opts,
 		clientset: clientset,
 		pods:      pod.NewPodManager(clientset),
@@ -52,19 +57,21 @@ func NewScanner(opts kube.ScannerOpts, clientset kubernetes.Interface) *Scanner
 	}
 }
 
-func (s *Scanner) Scan(ctx context.Context, workload kube.Object, gvk schema.GroupVersionKind) (report starboardv1alpha1.ConfigAudit, owner meta.Object, err error) {
-	_, owner, err = s.pods.GetPodSpecByWorkload(ctx, workload)
+func (s *Scanner) Scan(ctx context.Context, workload kube.Object, gvk schema.GroupVersionKind) (starboardv1alpha1.ConfigAuditReport, error) {
+	klog.V(3).Infof("Getting Pod template for workload: %v", workload)
+
+	_, owner, err := s.pods.GetPodSpecByWorkload(ctx, workload)
 	if err != nil {
-		return
+		return starboardv1alpha1.ConfigAuditReport{}, err
 	}
 
+	klog.V(3).Infof("Scanning with options: %+v", s.opts)
 	job := s.preparePolarisJob(workload, gvk)
 
 	err = runner.New().Run(ctx, kube.NewRunnableJob(s.clientset, job))
 	if err != nil {
 		s.pods.LogRunnerErrors(ctx, job)
-		err = fmt.Errorf("running polaris job: %w", err)
-		return
+		return starboardv1alpha1.ConfigAuditReport{}, fmt.Errorf("running polaris job: %w", err)
 	}
 
 	defer func() {
@@ -83,15 +90,18 @@ func (s *Scanner) Scan(ctx context.Context, workload kube.Object, gvk schema.Gro
 		job.Namespace, job.Name)
 	logsReader, err := s.pods.GetContainerLogsByJob(ctx, job, polarisContainerName)
 	if err != nil {
-		err = fmt.Errorf("getting logs: %w", err)
-		return
+		return starboardv1alpha1.ConfigAuditReport{}, fmt.Errorf("getting logs: %w", err)
 	}
 
-	report, err = s.converter.Convert(logsReader)
+	result, err := s.converter.Convert(logsReader)
 	defer func() {
 		_ = logsReader.Close()
 	}()
-	return
+
+	return configauditreport.NewBuilder(s.scheme).
+		Owner(owner).
+		Result(result).
+		Get()
 }
 
 func (s *Scanner) sourceNameFrom(workload kube.Object, gvk schema.GroupVersionKind) string {
diff --git a/pkg/polaris/writer.go b/pkg/polaris/writer.go
deleted file mode 100644
index 942fb18..0000000
--- a/pkg/polaris/writer.go
+++ /dev/null
@@ -1,30 +0,0 @@
-package polaris
-
-import (
-	"context"
-
-	"github.com/aquasecurity/starboard/pkg/kube"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-
-	sec "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
-	starboard "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
-)
-
-// Write is the interface that wraps basic methods for persisting ConfigAudit reports.
-//
-// Write persists the given ConfigAudit report.
-type Writer interface {
-	Write(ctx context.Context, report sec.ConfigAudit, owner metav1.Object) (err error)
-}
-
-// Reader is the interface that wraps basic methods for reading ConfigAudit reports.
-//
-// Read will return a single ConfigAuditReport that match a specific workload
-type Reader interface {
-	FindByOwner(ctx context.Context, owner kube.Object) (*starboard.ConfigAuditReport, error)
-}
-
-type ReadWriter interface {
-	Writer
-	Reader
-}
diff --git a/pkg/report/html.go b/pkg/report/html.go
index dd7ea37..e154473 100644
--- a/pkg/report/html.go
+++ b/pkg/report/html.go
@@ -6,21 +6,22 @@ import (
 	"fmt"
 	"io"
 
+	"github.com/aquasecurity/starboard/pkg/configauditreport"
+
 	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 
 	"github.com/aquasecurity/starboard/pkg/kube"
-	configAuditCrd "github.com/aquasecurity/starboard/pkg/polaris"
 	"github.com/aquasecurity/starboard/pkg/report/templates"
 )
 
 type htmlReporter struct {
 	vulnerabilityReportsReader vulnerabilityreport.ReadWriter
-	configAuditReportsReader   configAuditCrd.ReadWriter
+	configAuditReportsReader   configauditreport.ReadWriter
 }
 
-func NewHTMLReporter(configAuditReportsReader configAuditCrd.ReadWriter, vulnerabilityReportsReader vulnerabilityreport.ReadWriter) Reporter {
+func NewHTMLReporter(configAuditReportsReader configauditreport.ReadWriter, vulnerabilityReportsReader vulnerabilityreport.ReadWriter) Reporter {
 	return &htmlReporter{
 		vulnerabilityReportsReader: vulnerabilityReportsReader,
 		configAuditReportsReader:   configAuditReportsReader,
diff --git a/pkg/trivy/converter.go b/pkg/trivy/converter.go
index 400671f..45072cb 100644
--- a/pkg/trivy/converter.go
+++ b/pkg/trivy/converter.go
@@ -3,9 +3,10 @@ package trivy
 import (
 	"encoding/json"
 	"io"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"time"
 
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
 	"github.com/aquasecurity/starboard/pkg/starboard"
 
 	starboardv1alpha1 "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
diff --git a/pkg/vulnerabilityreport/builder.go b/pkg/vulnerabilityreport/builder.go
index 8065a8c..f750178 100644
--- a/pkg/vulnerabilityreport/builder.go
+++ b/pkg/vulnerabilityreport/builder.go
@@ -15,7 +15,7 @@ type Builder interface {
 	Owner(owner metav1.Object) Builder
 	Container(name string) Builder
 	PodSpecHash(hash string) Builder
-	ScanResult(result v1alpha1.VulnerabilityScanResult) Builder
+	Result(result v1alpha1.VulnerabilityScanResult) Builder
 	Get() (v1alpha1.VulnerabilityReport, error)
 }
 
@@ -48,7 +48,7 @@ func (b *builder) PodSpecHash(hash string) Builder {
 	return b
 }
 
-func (b *builder) ScanResult(result v1alpha1.VulnerabilityScanResult) Builder {
+func (b *builder) Result(result v1alpha1.VulnerabilityScanResult) Builder {
 	b.result = result
 	return b
 }
diff --git a/pkg/vulnerabilityreport/writer.go b/pkg/vulnerabilityreport/writer.go
index da08ed8..32e3926 100644
--- a/pkg/vulnerabilityreport/writer.go
+++ b/pkg/vulnerabilityreport/writer.go
@@ -6,7 +6,6 @@ import (
 	starboard "github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	"k8s.io/apimachinery/pkg/labels"
-	"k8s.io/apimachinery/pkg/runtime"
 	"k8s.io/klog"
 
 	clientset "github.com/aquasecurity/starboard/pkg/generated/clientset/versioned"
@@ -34,13 +33,11 @@ type ReadWriter interface {
 }
 
 type readWriter struct {
-	scheme *runtime.Scheme
 	client clientset.Interface
 }
 
-func NewReadWriter(scheme *runtime.Scheme, client clientset.Interface) ReadWriter {
+func NewReadWriter(client clientset.Interface) ReadWriter {
 	return &readWriter{
-		scheme: scheme,
 		client: client,
 	}
 }


Test patch:
diff --git a/pkg/configauditreport/builder_test.go b/pkg/configauditreport/builder_test.go
new file mode 100644
index 0000000..1ef6cf8
--- /dev/null
+++ b/pkg/configauditreport/builder_test.go
@@ -0,0 +1,47 @@
+package configauditreport_test
+
+import (
+	"testing"
+
+	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/configauditreport"
+	"github.com/stretchr/testify/assert"
+	"github.com/stretchr/testify/require"
+	appsv1 "k8s.io/api/apps/v1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/kubernetes/scheme"
+)
+
+func TestBuilder(t *testing.T) {
+	report, err := configauditreport.NewBuilder(scheme.Scheme).
+		Owner(&appsv1.ReplicaSet{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "some-owner",
+				Namespace: "qa",
+			},
+		}).
+		PodSpecHash("xyz").
+		Result(v1alpha1.ConfigAuditResult{}).Get()
+
+	require.NoError(t, err)
+	assert.Equal(t, v1alpha1.ConfigAuditReport{
+		ObjectMeta: metav1.ObjectMeta{
+			Name:      "replicaset-some-owner",
+			Namespace: "qa",
+			OwnerReferences: []metav1.OwnerReference{
+				{
+					APIVersion: "apps/v1",
+					Kind:       "ReplicaSet",
+					Name:       "some-owner",
+				},
+			},
+			Labels: map[string]string{
+				"starboard.resource.kind":      "ReplicaSet",
+				"starboard.resource.name":      "some-owner",
+				"starboard.resource.namespace": "qa",
+				"pod-spec-hash":                "xyz",
+			},
+		},
+		Report: v1alpha1.ConfigAuditResult{},
+	}, report)
+}
diff --git a/pkg/configauditreport/writer_test.go b/pkg/configauditreport/writer_test.go
new file mode 100644
index 0000000..87742fb
--- /dev/null
+++ b/pkg/configauditreport/writer_test.go
@@ -0,0 +1,122 @@
+package configauditreport_test
+
+import (
+	"context"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+
+	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
+	"github.com/aquasecurity/starboard/pkg/configauditreport"
+	"github.com/aquasecurity/starboard/pkg/generated/clientset/versioned/fake"
+	"github.com/aquasecurity/starboard/pkg/kube"
+	"github.com/stretchr/testify/require"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+)
+
+func TestReadWriter(t *testing.T) {
+
+	t.Run("Should create ConfigAuditReport", func(t *testing.T) {
+		clientset := fake.NewSimpleClientset()
+		err := configauditreport.NewReadWriter(clientset).Write(context.TODO(), v1alpha1.ConfigAuditReport{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "deployment-nginx",
+				Namespace: "qa",
+				Labels: map[string]string{
+					kube.LabelResourceKind:      "Deployment",
+					kube.LabelResourceName:      "nginx",
+					kube.LabelResourceNamespace: "qa",
+					kube.LabelPodSpecHash:       "abc",
+				},
+			},
+			Report: v1alpha1.ConfigAuditResult{},
+		})
+		require.NoError(t, err)
+
+		_, err = clientset.AquasecurityV1alpha1().ConfigAuditReports("qa").
+			Get(context.TODO(), "deployment-nginx", metav1.GetOptions{})
+		require.NoError(t, err)
+	})
+
+	t.Run("Should update ConfigAuditReport", func(t *testing.T) {
+		clientset := fake.NewSimpleClientset(&v1alpha1.ConfigAuditReport{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "deployment-wordpress",
+				Namespace: "prod",
+				Labels: map[string]string{
+					kube.LabelResourceKind:      "Deployment",
+					kube.LabelResourceName:      "wordpress",
+					kube.LabelResourceNamespace: "prod",
+					kube.LabelPodSpecHash:       "abc",
+				},
+			},
+			Report: v1alpha1.ConfigAuditResult{},
+		})
+		err := configauditreport.NewReadWriter(clientset).Write(context.TODO(), v1alpha1.ConfigAuditReport{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "deployment-wordpress",
+				Namespace: "prod",
+				Labels: map[string]string{
+					kube.LabelResourceKind:      "Deployment",
+					kube.LabelResourceName:      "wordpress",
+					kube.LabelResourceNamespace: "prod",
+					kube.LabelPodSpecHash:       "xyz",
+				},
+			},
+			Report: v1alpha1.ConfigAuditResult{},
+		})
+		require.NoError(t, err)
+		report, err := clientset.AquasecurityV1alpha1().ConfigAuditReports("prod").
+			Get(context.TODO(), "deployment-wordpress", metav1.GetOptions{})
+		require.NoError(t, err)
+		assert.Equal(t, map[string]string{
+			kube.LabelResourceKind:      "Deployment",
+			kube.LabelResourceName:      "wordpress",
+			kube.LabelResourceNamespace: "prod",
+			kube.LabelPodSpecHash:       "xyz",
+		}, report.Labels)
+	})
+
+	t.Run("Should find ConfigAuditReport", func(t *testing.T) {
+		clientset := fake.NewSimpleClientset(&v1alpha1.ConfigAuditReport{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "deployment-wordpress",
+				Namespace: "prod",
+				Labels: map[string]string{
+					kube.LabelResourceKind:      "Deployment",
+					kube.LabelResourceName:      "wordpress",
+					kube.LabelResourceNamespace: "prod",
+					kube.LabelPodSpecHash:       "abc",
+				},
+			},
+			Report: v1alpha1.ConfigAuditResult{},
+		}, &v1alpha1.ConfigAuditReport{
+			ObjectMeta: metav1.ObjectMeta{
+				Name:      "deployment-wordpress",
+				Namespace: "qa",
+				Labels: map[string]string{
+					kube.LabelResourceKind:      "Deployment",
+					kube.LabelResourceName:      "wordpress",
+					kube.LabelResourceNamespace: "qa",
+					kube.LabelPodSpecHash:       "abc",
+				},
+			},
+			Report: v1alpha1.ConfigAuditResult{},
+		})
+
+		report, err := configauditreport.NewReadWriter(clientset).FindByOwner(context.TODO(), kube.Object{
+			Kind:      kube.KindDeployment,
+			Name:      "wordpress",
+			Namespace: "qa",
+		})
+		require.NoError(t, err)
+		assert.Equal(t, "deployment-wordpress", report.Name)
+		assert.Equal(t, map[string]string{
+			kube.LabelResourceKind:      "Deployment",
+			kube.LabelResourceName:      "wordpress",
+			kube.LabelResourceNamespace: "qa",
+			kube.LabelPodSpecHash:       "abc",
+		}, report.Labels)
+	})
+
+}
diff --git a/pkg/polaris/converter_test.go b/pkg/polaris/converter_test.go
index 8edb5ab..95f3ac6 100644
--- a/pkg/polaris/converter_test.go
+++ b/pkg/polaris/converter_test.go
@@ -1,9 +1,11 @@
-package polaris
+package polaris_test
 
 import (
 	"os"
 	"testing"
 
+	"github.com/aquasecurity/starboard/pkg/polaris"
+
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/require"
@@ -18,9 +20,9 @@ func TestConverter_Convert(t *testing.T) {
 		_ = file.Close()
 	}()
 
-	reports, err := NewConverter().Convert(file)
+	reports, err := polaris.NewConverter().Convert(file)
 	require.NoError(t, err)
-	assert.Equal(t, []v1alpha1.ConfigAudit{
+	assert.Equal(t, []v1alpha1.ConfigAuditResult{
 		{
 			Scanner: v1alpha1.Scanner{
 				Name:    "Polaris",
diff --git a/pkg/report/html_test.go b/pkg/report/html_test.go
index a2d7552..ee60a74 100644
--- a/pkg/report/html_test.go
+++ b/pkg/report/html_test.go
@@ -56,7 +56,7 @@ var (
 		}}
 	sampleConfigAudits = []starboard.ConfigAuditReport{
 		{
-			Report: starboard.ConfigAudit{
+			Report: starboard.ConfigAuditResult{
 
 				Scanner: starboard.Scanner{
 					Name:    "Polaris",
diff --git a/pkg/vulnerabilityreport/builder_test.go b/pkg/vulnerabilityreport/builder_test.go
index e6d4a9e..c8cd3dd 100644
--- a/pkg/vulnerabilityreport/builder_test.go
+++ b/pkg/vulnerabilityreport/builder_test.go
@@ -22,7 +22,7 @@ func TestBuilder(t *testing.T) {
 		}).
 		Container("my-container").
 		PodSpecHash("xyz").
-		ScanResult(v1alpha1.VulnerabilityScanResult{}).Get()
+		Result(v1alpha1.VulnerabilityScanResult{}).Get()
 
 	require.NoError(t, err)
 	assert.Equal(t, v1alpha1.VulnerabilityReport{
diff --git a/pkg/vulnerabilityreport/writer_test.go b/pkg/vulnerabilityreport/writer_test.go
index c849f2e..2742d61 100644
--- a/pkg/vulnerabilityreport/writer_test.go
+++ b/pkg/vulnerabilityreport/writer_test.go
@@ -4,7 +4,8 @@ import (
 	"context"
 	"testing"
 
-	"github.com/aquasecurity/starboard/pkg/starboard"
+	"github.com/stretchr/testify/assert"
+	"k8s.io/apimachinery/pkg/labels"
 
 	"github.com/aquasecurity/starboard/pkg/vulnerabilityreport"
 
@@ -15,74 +16,150 @@ import (
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 )
 
-var (
-	vulnerabilityReport01 = v1alpha1.VulnerabilityScanResult{
-		Vulnerabilities: []v1alpha1.Vulnerability{
-			{VulnerabilityID: "CVE-2020-1832"},
-		},
-	}
-	vulnerabilityReport02 = v1alpha1.VulnerabilityScanResult{
-		Vulnerabilities: []v1alpha1.Vulnerability{
-			{VulnerabilityID: "CVE-2019-8211"},
-		},
-	}
-)
-
 func TestReadWriter_Read(t *testing.T) {
-	clientset := fake.NewSimpleClientset(&v1alpha1.VulnerabilityReport{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       "Vulnerability",
-			APIVersion: "v1alpha1",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: "my-namespace",
-			Name:      "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",
+
+	t.Run("Should create VulnerabilityReport", func(t *testing.T) {
+		clientset := fake.NewSimpleClientset()
+		err := vulnerabilityreport.NewReadWriter(clientset).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",
+					},
+				},
 			},
-		},
-		Report: vulnerabilityReport01,
-	}, &v1alpha1.VulnerabilityReport{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       "Vulnerability",
-			APIVersion: "v1alpha1",
-		},
-		ObjectMeta: metav1.ObjectMeta{
-			Namespace: "my-namespace",
-			Name:      "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",
+			{
+				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",
+					},
+				},
 			},
-		},
-		Report: vulnerabilityReport02,
-	}, &v1alpha1.VulnerabilityReport{
-		TypeMeta: metav1.TypeMeta{
-			Kind:       "Vulnerability",
-			APIVersion: "v1alpha1",
-		},
-		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",
+		})
+		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]bool{}
+		for _, item := range list.Items {
+			reports[item.Name] = true
+		}
+		assert.Equal(t, map[string]bool{
+			"deployment-app-c1": true,
+			"deployment-app-c2": true,
+		}, reports)
+	})
+
+	t.Run("Should update VulnerabilityReport", func(t *testing.T) {
+		clientset := fake.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",
+				},
 			},
-		},
-		Report: v1alpha1.VulnerabilityScanResult{},
+		})
+		err := vulnerabilityreport.NewReadWriter(clientset).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, "deployment-nginx", report.Name)
+		assert.Equal(t, map[string]string{
+			kube.LabelResourceKind:      "Deployment",
+			kube.LabelResourceName:      "nginx",
+			kube.LabelResourceNamespace: "qa",
+			kube.LabelContainerName:     "nginx",
+			kube.LabelPodSpecHash:       "xyz",
+		}, report.Labels)
 	})
 
-	_, err := vulnerabilityreport.NewReadWriter(starboard.NewScheme(), clientset).FindByOwner(context.TODO(), kube.Object{
-		Kind:      kube.KindDeployment,
-		Name:      "my-deploy",
-		Namespace: "my-namespace",
+	t.Run("Should find VulnerabilityReports", func(t *testing.T) {
+		clientset := fake.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{},
+		})
+
+		list, err := vulnerabilityreport.NewReadWriter(clientset).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)
 	})
-	require.NoError(t, err)
+
 }