Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
43 changes: 28 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,26 @@ 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.
ns = strings.TrimSpace(ns)
if ns == "" {
continue
}
result[ns] = cache.Config{}
}
if len(result) == 0 {
return nil
}
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