Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ And then handle it accordingly in your monitor's implementation.

#### Examples

In [endpointmonitor_types.go](./pkg/apis/endpointmonitor/v1alpha1/endpointmonitor_types.go)
In [endpointmonitor_types.go](./api/v1alpha1/endpointmonitor_types.go)

```yaml
// UptimeRobotConfig defines the configuration for UptimeRobot Monitor Provider
Expand Down
36 changes: 21 additions & 15 deletions cmd/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,24 +142,17 @@ func main() {
Metrics: metricsServerOptions,
}

namespaces := []string{}
// Add support for MultiNamespace set in WATCH_NAMESPACE (e.g ns1,ns2)
// More Info: https://godoc.org/github.com/kubernetes-sigs/controller-runtime/pkg/cache#MultiNamespacedCacheBuilder
if strings.Contains(watchNamespace, ",") {
setupLog.Info("Manager will be watching namespace(s) %q", watchNamespace)
// configure cluster-scoped with MultiNamespacedCacheBuilder
//options.Namespace = ""
namespaces = strings.Split(watchNamespace, ",")
} else {
namespaces = []string{watchNamespace}
}

options.NewCache = func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
opts.DefaultNamespaces = map[string]cache.Config{}
for _, ns := range namespaces {
opts.DefaultNamespaces[ns] = cache.Config{}
// Only restrict the cache when a non-empty WATCH_NAMESPACE is set. When empty, leave DefaultNamespaces nil
// so the manager uses a global (all-namespace) cache. Setting DefaultNamespaces = {"": {}} would trigger
// multiNamespaceCache whose List() does not fall back to the global informer for specific namespaces.
if defaultNamespaces := buildDefaultNamespaces(watchNamespace); defaultNamespaces != nil {
setupLog.Info("Manager will be watching namespace(s)", "namespaces", watchNamespace)
options.NewCache = func(config *rest.Config, opts cache.Options) (cache.Cache, error) {
opts.DefaultNamespaces = defaultNamespaces
return cache.New(config, opts)
}
return cache.New(config, opts)
}

mgr, err := ctrl.NewManager(ctrl.GetConfigOrDie(), options)
Expand Down Expand Up @@ -199,6 +192,19 @@ func main() {
}
}

// buildDefaultNamespaces returns the cache.Config map for the given comma-separated
// watchNamespace value, or nil when watchNamespace is empty (cluster-scoped cache).
func buildDefaultNamespaces(watchNamespace string) map[string]cache.Config {
if watchNamespace == "" {
return nil
}
result := map[string]cache.Config{}
for _, ns := range strings.Split(watchNamespace, ",") {
Comment thread
SyedaFatimaKazmi marked this conversation as resolved.
result[strings.TrimSpace(ns)] = cache.Config{}
}
return result
}

func getWatchNamespace() (string, error) {
// WatchNamespaceEnvVar is the constant for env variable WATCH_NAMESPACE
// which specifies the Namespace to watch.
Expand Down
123 changes: 123 additions & 0 deletions cmd/main_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package main

import (
"os"
"testing"
)

func TestBuildDefaultNamespaces(t *testing.T) {
tests := []struct {
name string
watchNamespace string
wantNil bool
wantKeys []string
}{
{
name: "empty string returns nil (cluster-scoped cache)",
watchNamespace: "",
wantNil: true,
},
{
name: "single namespace",
watchNamespace: "my-namespace",
wantKeys: []string{"my-namespace"},
},
{
name: "multiple namespaces",
watchNamespace: "ns1,ns2,ns3",
wantKeys: []string{"ns1", "ns2", "ns3"},
},
{
name: "namespaces with surrounding spaces are trimmed",
watchNamespace: "ns1, ns2 , ns3",
wantKeys: []string{"ns1", "ns2", "ns3"},
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := buildDefaultNamespaces(tt.watchNamespace)

if tt.wantNil {
if got != nil {
t.Errorf("expected nil, got %v", got)
}
return
}

if got == nil {
t.Fatal("expected non-nil map, got nil")
}

if len(got) != len(tt.wantKeys) {
t.Errorf("expected %d namespaces, got %d: %v", len(tt.wantKeys), len(got), got)
}

for _, key := range tt.wantKeys {
if _, ok := got[key]; !ok {
t.Errorf("expected key %q in map, got %v", key, got)
}
}
})
}
}

func TestGetWatchNamespace(t *testing.T) {
tests := []struct {
name string
envVal string
envSet bool
wantNS string
wantErr bool
}{
{
name: "env var not set returns error",
envSet: false,
wantErr: true,
},
{
name: "empty env var returns empty string (cluster-scoped)",
envSet: true,
envVal: "",
wantNS: "",
},
{
name: "single namespace",
envSet: true,
envVal: "my-namespace",
wantNS: "my-namespace",
},
{
name: "multiple namespaces passed through as-is",
envSet: true,
envVal: "ns1,ns2",
wantNS: "ns1,ns2",
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
os.Unsetenv("WATCH_NAMESPACE")
if tt.envSet {
os.Setenv("WATCH_NAMESPACE", tt.envVal)
defer os.Unsetenv("WATCH_NAMESPACE")
}

got, err := getWatchNamespace()

if tt.wantErr {
if err == nil {
t.Error("expected error, got nil")
}
return
}

if err != nil {
t.Errorf("unexpected error: %v", err)
}
if got != tt.wantNS {
t.Errorf("expected %q, got %q", tt.wantNS, got)
}
})
}
}
Loading