Repository: aquasecurity/starboard
Pull request #362 (2021-01-26 09:13:49)

fix(operator): Skip workloads in the operator namespace
Signed-off-by: Daniel Pacak <pacak.daniel@gmail.com>

codecov[bot]: # [Codecov](https://codecov.io/gh/aquasecurity/starboard/pull/362?src=pr&el=h1) Report
> Merging [#362](https://codecov.io/gh/aquasecurity/starboard/pull/362?src=pr&el=desc) (58a10a2) into [main](https://codecov.io/gh/aquasecurity/starboard/commit/81414f86c449ed5b3c338823ca34d82d6c2cfec9?el=desc) (81414f8) will **decrease** coverage by `36.56%`.
> The diff coverage is `44.44%`.

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

```diff
@@             Coverage Diff             @@
##             main     #362       +/-   ##
===========================================
- Coverage   66.68%   30.12%   -36.57%     
===========================================
  Files          58       57        -1     
  Lines        3056     2968       -88     
===========================================
- Hits         2038      894     -1144     
- Misses        769     2011     +1242     
+ Partials      249       63      -186     
```


| [Impacted Files](https://codecov.io/gh/aquasecurity/starboard/pull/362?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [pkg/operator/controller/configauditreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/362/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvY29uZmlnYXVkaXRyZXBvcnQuZ28=) | `0.00% <0.00%> (-55.87%)` | :arrow_down: |
| [pkg/operator/controller/vulnerabilityreport.go](https://codecov.io/gh/aquasecurity/starboard/pull/362/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2NvbnRyb2xsZXIvdnVsbmVyYWJpbGl0eXJlcG9ydC5nbw==) | `0.00% <0.00%> (-58.45%)` | :arrow_down: |
| [pkg/operator/predicate/predicate.go](https://codecov.io/gh/aquasecurity/starboard/pull/362/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL3ByZWRpY2F0ZS9wcmVkaWNhdGUuZ28=) | `75.47% <72.72%> (-19.77%)` | :arrow_down: |
| [pkg/operator/etc/config.go](https://codecov.io/gh/aquasecurity/starboard/pull/362/diff?src=pr&el=tree#diff-cGtnL29wZXJhdG9yL2V0Yy9jb25maWcuZ28=) | `72.72% <80.00%> (ø)` | |
| [pkg/cmd/get.go](https://codecov.io/gh/aquasecurity/starboard/pull/362/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/362/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/362/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/362/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/362/diff?src=pr&el=tree#diff-cGtnL2NtZC9wb2xhcmlzLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| [pkg/cmd/version.go](https://codecov.io/gh/aquasecurity/starboard/pull/362/diff?src=pr&el=tree#diff-cGtnL2NtZC92ZXJzaW9uLmdv) | `0.00% <0.00%> (-100.00%)` | :arrow_down: |
| ... and [31 more](https://codecov.io/gh/aquasecurity/starboard/pull/362/diff?src=pr&el=tree-more) | |

------

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


Patch:
diff --git a/pkg/operator/controller/configauditreport.go b/pkg/operator/controller/configauditreport.go
index 6f1cb05..375a6a2 100644
--- a/pkg/operator/controller/configauditreport.go
+++ b/pkg/operator/controller/configauditreport.go
@@ -35,11 +35,16 @@ type ConfigAuditReportReconciler struct {
 }
 
 func (r *ConfigAuditReportReconciler) SetupWithManager(mgr ctrl.Manager) error {
-	err := ctrl.NewControllerManagedBy(mgr).
+	installModePredicate, err := InstallModePredicate(r.Config)
+	if err != nil {
+		return err
+	}
+	err = ctrl.NewControllerManagedBy(mgr).
 		For(&corev1.Pod{}, builder.WithPredicates(
 			Not(ManagedByStarboardOperator),
 			Not(PodBeingTerminated),
 			PodHasContainersReadyCondition,
+			installModePredicate,
 		)).
 		Complete(r.reconcilePods())
 	if err != nil {
diff --git a/pkg/operator/controller/vulnerabilityreport.go b/pkg/operator/controller/vulnerabilityreport.go
index 431bda1..a02e094 100644
--- a/pkg/operator/controller/vulnerabilityreport.go
+++ b/pkg/operator/controller/vulnerabilityreport.go
@@ -7,7 +7,6 @@ import (
 
 	"github.com/aquasecurity/starboard/pkg/apis/aquasecurity/v1alpha1"
 	"github.com/aquasecurity/starboard/pkg/docker"
-	"github.com/aquasecurity/starboard/pkg/ext"
 	"github.com/aquasecurity/starboard/pkg/kube"
 	"github.com/aquasecurity/starboard/pkg/operator/etc"
 	. "github.com/aquasecurity/starboard/pkg/operator/predicate"
@@ -18,7 +17,6 @@ import (
 	corev1 "k8s.io/api/core/v1"
 	"k8s.io/apimachinery/pkg/api/errors"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/types"
 	"k8s.io/utils/pointer"
 	ctrl "sigs.k8s.io/controller-runtime"
 	"sigs.k8s.io/controller-runtime/pkg/builder"
@@ -40,11 +38,16 @@ type VulnerabilityReportReconciler struct {
 }
 
 func (r *VulnerabilityReportReconciler) SetupWithManager(mgr ctrl.Manager) error {
-	err := ctrl.NewControllerManagedBy(mgr).
+	installModePredicate, err := InstallModePredicate(r.Config)
+	if err != nil {
+		return err
+	}
+	err = ctrl.NewControllerManagedBy(mgr).
 		For(&corev1.Pod{}, builder.WithPredicates(
 			Not(ManagedByStarboardOperator),
 			Not(PodBeingTerminated),
 			PodHasContainersReadyCondition,
+			installModePredicate,
 		)).
 		Complete(r.reconcilePods())
 	if err != nil {
@@ -66,19 +69,8 @@ func (r *VulnerabilityReportReconciler) reconcilePods() reconcile.Func {
 
 		pod := &corev1.Pod{}
 
-		installMode, err := r.GetInstallMode()
-		if err != nil {
-			return ctrl.Result{}, fmt.Errorf("getting install mode: %w", err)
-		}
-
-		// TODO Consider using a predicate
-		if r.IgnorePodInOperatorNamespace(installMode, req.NamespacedName) {
-			log.V(1).Info("Ignoring Pod run in the operator namespace")
-			return ctrl.Result{}, nil
-		}
-
 		// Retrieve the Pod from cache.
-		err = r.Get(ctx, req.NamespacedName, pod)
+		err := r.Get(ctx, req.NamespacedName, pod)
 		if err != nil {
 			if errors.IsNotFound(err) {
 				log.V(1).Info("Ignoring pod that must have been deleted")
@@ -148,7 +140,7 @@ func (r *VulnerabilityReportReconciler) hasVulnerabilityReports(ctx context.Cont
 	}
 
 	expected := map[string]bool{}
-	for containerName:= range images {
+	for containerName := range images {
 		expected[containerName] = true
 	}
 
@@ -376,32 +368,3 @@ func (r *VulnerabilityReportReconciler) processFailedScanJob(ctx context.Context
 	log.V(1).Info("Deleting failed scan job")
 	return r.Client.Delete(ctx, scanJob, client.PropagationPolicy(metav1.DeletePropagationBackground))
 }
-
-// IgnorePodInOperatorNamespace determines whether to reconcile the specified Pod
-// based on the give InstallMode or not. Returns true if the Pod should be ignored,
-// false otherwise.
-//
-// In the SingleNamespace install mode we're configuring Client cache
-// to watch the operator namespace, in which the operator runs scan Jobs.
-// However, we do not want to scan the workloads that might run in the
-// operator namespace.
-//
-// In the MultiNamespace install mode we're configuring Client cache
-// to watch the operator namespace, in which the operator runs scan Jobs.
-// However, we do not want to scan the workloads that might run in the
-// operator namespace unless the operator namespace is added to the list
-// of target namespaces.
-func (r *VulnerabilityReportReconciler) IgnorePodInOperatorNamespace(installMode etc.InstallMode, pod types.NamespacedName) bool {
-	if installMode == etc.InstallModeSingleNamespace &&
-		pod.Namespace == r.Namespace {
-		return true
-	}
-
-	if installMode == etc.InstallModeMultiNamespace &&
-		pod.Namespace == r.Namespace &&
-		!ext.SliceContainsString(r.GetTargetNamespaces(), r.Namespace) {
-		return true
-	}
-
-	return false
-}
diff --git a/pkg/operator/etc/config.go b/pkg/operator/etc/config.go
index e6e2550..40dfd0d 100644
--- a/pkg/operator/etc/config.go
+++ b/pkg/operator/etc/config.go
@@ -48,28 +48,28 @@ func (c Config) GetTargetNamespaces() []string {
 type InstallMode string
 
 const (
-	InstallModeOwnNamespace    InstallMode = "OwnNamespace"
-	InstallModeSingleNamespace InstallMode = "SingleNamespace"
-	InstallModeMultiNamespace  InstallMode = "MultiNamespace"
-	InstallModeAllNamespaces   InstallMode = "AllNamespaces"
+	OwnNamespace    InstallMode = "OwnNamespace"
+	SingleNamespace InstallMode = "SingleNamespace"
+	MultiNamespace  InstallMode = "MultiNamespace"
+	AllNamespaces   InstallMode = "AllNamespaces"
 )
 
-// GetInstallMode resolves InstallMode based on configured operator and target namespaces.
-func (c Config) GetInstallMode() (InstallMode, error) {
+// ResolveInstallMode resolves InstallMode based on configured Config.Namespace and Config.TargetNamespaces.
+func (c Config) ResolveInstallMode() (InstallMode, string, []string, error) {
 	operatorNamespace, err := c.GetOperatorNamespace()
 	if err != nil {
-		return "", nil
+		return "", "", nil, err
 	}
 	targetNamespaces := c.GetTargetNamespaces()
 
 	if len(targetNamespaces) == 1 && operatorNamespace == targetNamespaces[0] {
-		return InstallModeOwnNamespace, nil
+		return OwnNamespace, operatorNamespace, targetNamespaces, nil
 	}
 	if len(targetNamespaces) == 1 && operatorNamespace != targetNamespaces[0] {
-		return InstallModeSingleNamespace, nil
+		return SingleNamespace, operatorNamespace, targetNamespaces, nil
 	}
 	if len(targetNamespaces) > 1 {
-		return InstallModeMultiNamespace, nil
+		return MultiNamespace, operatorNamespace, targetNamespaces, nil
 	}
-	return InstallModeAllNamespaces, nil
+	return AllNamespaces, operatorNamespace, targetNamespaces, nil
 }
diff --git a/pkg/operator/operator.go b/pkg/operator/operator.go
index 9664e9a..39dd02f 100644
--- a/pkg/operator/operator.go
+++ b/pkg/operator/operator.go
@@ -26,19 +26,11 @@ var (
 func Run(buildInfo starboard.BuildInfo, operatorConfig etc.Config) error {
 	setupLog.Info("Starting operator", "buildInfo", buildInfo)
 
-	// Validate configured namespaces to resolve install mode.
-	operatorNamespace, err := operatorConfig.GetOperatorNamespace()
+	installMode, operatorNamespace, targetNamespaces, err := operatorConfig.ResolveInstallMode()
 	if err != nil {
-		return fmt.Errorf("getting operator namespace: %w", err)
+		return fmt.Errorf("resolving install mode: %w", err)
 	}
-
-	targetNamespaces := operatorConfig.GetTargetNamespaces()
-
-	installMode, err := operatorConfig.GetInstallMode()
-	if err != nil {
-		return fmt.Errorf("getting install mode: %w", err)
-	}
-	setupLog.Info("Resolving install mode", "install mode", installMode,
+	setupLog.Info("Resolved install mode", "install mode", installMode,
 		"operator namespace", operatorNamespace,
 		"target namespaces", targetNamespaces)
 
@@ -50,26 +42,30 @@ func Run(buildInfo starboard.BuildInfo, operatorConfig etc.Config) error {
 	}
 
 	switch installMode {
-	case etc.InstallModeOwnNamespace:
-		// Add support for OwnNamespace set in STARBOARD_NAMESPACE (e.g. marketplace) and STARBOARD_TARGET_NAMESPACES (e.g. marketplace)
-		setupLog.Info("Constructing single-namespaced cache", "namespace", targetNamespaces[0])
+	case etc.OwnNamespace:
+		// Add support for OwnNamespace set in OPERATOR_NAMESPACE (e.g. `starboard-operator`)
+		// and OPERATOR_TARGET_NAMESPACES (e.g. `starboard-operator`).
+		setupLog.Info("Constructing client cache", "namespace", targetNamespaces[0])
 		options.Namespace = targetNamespaces[0]
-	case etc.InstallModeSingleNamespace:
-		// Add support for SingleNamespace set in STARBOARD_NAMESPACE (e.g. marketplace) and STARBOARD_TARGET_NAMESPACES (e.g. foo)
+	case etc.SingleNamespace:
+		// Add support for SingleNamespace set in OPERATOR_NAMESPACE (e.g. `starboard-operator`)
+		// and OPERATOR_TARGET_NAMESPACES (e.g. `default`).
 		cachedNamespaces := append(targetNamespaces, operatorNamespace)
-		setupLog.Info("Constructing multi-namespaced cache", "namespaces", cachedNamespaces)
+		setupLog.Info("Constructing client cache", "namespaces", cachedNamespaces)
 		options.Namespace = targetNamespaces[0]
 		options.NewCache = cache.MultiNamespacedCacheBuilder(cachedNamespaces)
-	case etc.InstallModeMultiNamespace:
-		// Add support for MultiNamespace set in STARBOARD_NAMESPACE (e.g. marketplace) and STARBOARD_TARGET_NAMESPACES (e.g. foo,bar).
-		// Note that we may face performance issues when using this with a high number of namespaces.
+	case etc.MultiNamespace:
+		// Add support for MultiNamespace set in OPERATOR_NAMESPACE (e.g. `starboard-operator`)
+		// and OPERATOR_TARGET_NAMESPACES (e.g. `default,kube-system`).
+		// Note that you may face performance issues when using this mode with a high number of namespaces.
 		// More: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/cache#MultiNamespacedCacheBuilder
 		cachedNamespaces := append(targetNamespaces, operatorNamespace)
-		setupLog.Info("Constructing multi-namespaced cache", "namespaces", cachedNamespaces)
+		setupLog.Info("Constructing client cache", "namespaces", cachedNamespaces)
 		options.Namespace = ""
 		options.NewCache = cache.MultiNamespacedCacheBuilder(cachedNamespaces)
-	case etc.InstallModeAllNamespaces:
-		// Add support for AllNamespaces set in STARBOARD_NAMESPACE (e.g. marketplace) and STARBOARD_TARGET_NAMESPACES left blank.
+	case etc.AllNamespaces:
+		// Add support for AllNamespaces set in OPERATOR_NAMESPACE (e.g. `operators`)
+		// and OPERATOR_TARGET_NAMESPACES left blank.
 		setupLog.Info("Watching all namespaces")
 		options.Namespace = ""
 	default:
diff --git a/pkg/operator/predicate/predicate.go b/pkg/operator/predicate/predicate.go
index 5ca2d23..a2cd08b 100644
--- a/pkg/operator/predicate/predicate.go
+++ b/pkg/operator/predicate/predicate.go
@@ -1,7 +1,9 @@
 package predicate
 
 import (
+	"github.com/aquasecurity/starboard/pkg/ext"
 	"github.com/aquasecurity/starboard/pkg/kube"
+	"github.com/aquasecurity/starboard/pkg/operator/etc"
 	batchv1 "k8s.io/api/batch/v1"
 	corev1 "k8s.io/api/core/v1"
 	"sigs.k8s.io/controller-runtime/pkg/client"
@@ -9,6 +11,38 @@ import (
 	"sigs.k8s.io/controller-runtime/pkg/predicate"
 )
 
+// InstallModePredicate is a predicate.Predicate that determines whether to
+// reconcile the specified client.Object based on the give etc.InstallMode.
+//
+// In the etc.SingleNamespace install mode we're configuring client.Client cache
+// to watch the operator namespace, in which the operator runs scan jobs.
+// However, we do not want to scan the workloads that might run in the
+// operator namespace.
+//
+// Similarly, in the etc.MultiNamespace install mode we're configuring
+// client.Client cache to watch the operator namespace, in which the operator
+// runs scan jobs. However, we do not want to scan the workloads that might run
+// in the operator namespace unless the operator namespace is added to the list
+// of target namespaces.
+var InstallModePredicate = func(config etc.Config) (predicate.Predicate, error) {
+	mode, operatorNamespace, targetNamespaces, err := config.ResolveInstallMode()
+	if err != nil {
+		return nil, err
+	}
+	return predicate.NewPredicateFuncs(func(obj client.Object) bool {
+		if mode == etc.SingleNamespace {
+			return targetNamespaces[0] == obj.GetNamespace() &&
+				operatorNamespace != obj.GetNamespace()
+		}
+
+		if mode == etc.MultiNamespace {
+			return ext.SliceContainsString(targetNamespaces, obj.GetNamespace())
+		}
+
+		return true
+	}), nil
+}
+
 // InNamespace is a predicate.Predicate that returns true if the
 // specified client.Object is in the desired namespace.
 var InNamespace = func(namespace string) predicate.Predicate {


Test patch:
diff --git a/pkg/operator/etc/config_test.go b/pkg/operator/etc/config_test.go
index 5c485f6..eeb30af 100644
--- a/pkg/operator/etc/config_test.go
+++ b/pkg/operator/etc/config_test.go
@@ -44,7 +44,7 @@ func TestOperator_GetTargetNamespaces(t *testing.T) {
 	}
 }
 
-func TestOperator_GetInstallMode(t *testing.T) {
+func TestOperator_ResolveInstallMode(t *testing.T) {
 	testCases := []struct {
 		name string
 
@@ -58,7 +58,7 @@ func TestOperator_GetInstallMode(t *testing.T) {
 				Namespace:        "operators",
 				TargetNamespaces: "operators",
 			},
-			expectedInstallMode: etc.InstallModeOwnNamespace,
+			expectedInstallMode: etc.OwnNamespace,
 			expectedError:       "",
 		},
 		{
@@ -67,7 +67,7 @@ func TestOperator_GetInstallMode(t *testing.T) {
 				Namespace:        "operators",
 				TargetNamespaces: "foo",
 			},
-			expectedInstallMode: etc.InstallModeSingleNamespace,
+			expectedInstallMode: etc.SingleNamespace,
 			expectedError:       "",
 		},
 		{
@@ -76,7 +76,7 @@ func TestOperator_GetInstallMode(t *testing.T) {
 				Namespace:        "operators",
 				TargetNamespaces: "foo,bar,baz",
 			},
-			expectedInstallMode: etc.InstallModeMultiNamespace,
+			expectedInstallMode: etc.MultiNamespace,
 			expectedError:       "",
 		},
 		{
@@ -85,14 +85,14 @@ func TestOperator_GetInstallMode(t *testing.T) {
 				Namespace:        "operators",
 				TargetNamespaces: "",
 			},
-			expectedInstallMode: etc.InstallModeAllNamespaces,
+			expectedInstallMode: etc.AllNamespaces,
 			expectedError:       "",
 		},
 	}
 
 	for _, tc := range testCases {
 		t.Run(tc.name, func(t *testing.T) {
-			installMode, err := tc.operator.GetInstallMode()
+			installMode, _, _, err := tc.operator.ResolveInstallMode()
 			switch tc.expectedError {
 			case "":
 				require.NoError(t, err)
diff --git a/pkg/operator/predicate/predicate_test.go b/pkg/operator/predicate/predicate_test.go
index 675345b..69c3803 100644
--- a/pkg/operator/predicate/predicate_test.go
+++ b/pkg/operator/predicate/predicate_test.go
@@ -3,6 +3,7 @@ package predicate_test
 import (
 	"time"
 
+	"github.com/aquasecurity/starboard/pkg/operator/etc"
 	"github.com/aquasecurity/starboard/pkg/operator/predicate"
 	. "github.com/onsi/ginkgo"
 	. "github.com/onsi/gomega"
@@ -16,6 +17,110 @@ import (
 
 var _ = Describe("Predicate", func() {
 
+	Describe("When checking a InstallMode predicate", func() {
+
+		Context("When install mode is SingleNamespace", func() {
+
+			When("and object is in operator namespace", func() {
+
+				It("Should return false", func() {
+					config := etc.Config{
+						Namespace:        "starboard-operator",
+						TargetNamespaces: "default",
+					}
+					obj := &corev1.Pod{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "starboard-operator",
+						},
+					}
+					instance, err := predicate.InstallModePredicate(config)
+					Expect(err).ToNot(HaveOccurred())
+
+					Expect(instance.Create(event.CreateEvent{Object: obj})).To(BeFalse())
+					Expect(instance.Update(event.UpdateEvent{ObjectNew: obj})).To(BeFalse())
+					Expect(instance.Delete(event.DeleteEvent{Object: obj})).To(BeFalse())
+					Expect(instance.Generic(event.GenericEvent{Object: obj})).To(BeFalse())
+
+				})
+
+			})
+
+			When("and object is in target namespace", func() {
+
+				It("Should return true", func() {
+					config := etc.Config{
+						Namespace:        "starboard-operator",
+						TargetNamespaces: "foo",
+					}
+					obj := &corev1.Pod{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "foo",
+						},
+					}
+					instance, err := predicate.InstallModePredicate(config)
+					Expect(err).ToNot(HaveOccurred())
+
+					Expect(instance.Create(event.CreateEvent{Object: obj})).To(BeTrue())
+					Expect(instance.Update(event.UpdateEvent{ObjectNew: obj})).To(BeTrue())
+					Expect(instance.Delete(event.DeleteEvent{Object: obj})).To(BeTrue())
+					Expect(instance.Generic(event.GenericEvent{Object: obj})).To(BeTrue())
+				})
+
+			})
+
+		})
+
+		Context("When install mode is MultiNamespaces", func() {
+
+			When("and object is in target namespace", func() {
+
+				It("Should return true", func() {
+					config := etc.Config{
+						Namespace:        "starboard-operator",
+						TargetNamespaces: "foo,starboard-operator",
+					}
+					obj := &corev1.Pod{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "starboard-operator",
+						},
+					}
+					instance, err := predicate.InstallModePredicate(config)
+					Expect(err).ToNot(HaveOccurred())
+
+					Expect(instance.Create(event.CreateEvent{Object: obj})).To(BeTrue())
+					Expect(instance.Update(event.UpdateEvent{ObjectNew: obj})).To(BeTrue())
+					Expect(instance.Delete(event.DeleteEvent{Object: obj})).To(BeTrue())
+					Expect(instance.Generic(event.GenericEvent{Object: obj})).To(BeTrue())
+				})
+
+			})
+
+			When("and object is not in target namespace", func() {
+
+				It("Should return false", func() {
+					config := etc.Config{
+						Namespace:        "starboard-operator",
+						TargetNamespaces: "foo,bar",
+					}
+					obj := &corev1.Pod{
+						ObjectMeta: metav1.ObjectMeta{
+							Namespace: "starboard-operator",
+						},
+					}
+					instance, err := predicate.InstallModePredicate(config)
+					Expect(err).ToNot(HaveOccurred())
+
+					Expect(instance.Create(event.CreateEvent{Object: obj})).To(BeFalse())
+					Expect(instance.Update(event.UpdateEvent{ObjectNew: obj})).To(BeFalse())
+					Expect(instance.Delete(event.DeleteEvent{Object: obj})).To(BeFalse())
+					Expect(instance.Generic(event.GenericEvent{Object: obj})).To(BeFalse())
+				})
+
+			})
+
+		})
+	})
+
 	Describe("When checking a InNamespace predicate", func() {
 
 		Context("When object is in desired namespace", func() {