diff --git a/main.go b/main.go index f78e901..7df7df1 100644 --- a/main.go +++ b/main.go @@ -125,6 +125,7 @@ func main() { secretName := flag.String("secret-name", "k8s-athenz-syncer", "secret name that contains private key") header := flag.String("auth-header", "", "Authentication header field") nTokenExpireTime := flag.String("ntoken-expiry", "1h0m0s", "Custom nToken expiration duration") + excludeNamespaces := flag.String("exclude-namespaces", "", "Namespaces to exclude from processing ex: 'kube-system,kube-public,acceptance-test'") klog.InitFlags(nil) flag.Set("logtostderr", "false") @@ -190,7 +191,17 @@ func main() { processList = append(processList, item) } } - util := util.NewUtil(*adminDomain, processList) + + // process skip namespaces input string + excludeNSList := strings.Split(*excludeNamespaces, ",") + exclusionList := []string{} + for _, item := range excludeNSList { + item := strings.TrimSpace(item) + if item != "" { + exclusionList = append(exclusionList, item) + } + } + util := util.NewUtil(*adminDomain, processList, exclusionList) // construct the Controller object which has all of the necessary components to // handle logging, connections, informing (listing and watching), the queue, diff --git a/pkg/controller/controller.go b/pkg/controller/controller.go index 646077b..6649979 100644 --- a/pkg/controller/controller.go +++ b/pkg/controller/controller.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -122,6 +122,10 @@ func (c *Controller) nsinformerhandler(fn cache.KeyFunc, obj interface{}) string log.Errorf("Error returned from Key Func in nsInformerHandler. Error: %v", err) return "" } + if c.util.IsNamespaceExcluded(key) { + log.Infof("Skip processing excluded namespace: %s", key) + return key + } domain := c.util.NamespaceToDomain(key) c.queue.AddRateLimited(domain) return key diff --git a/pkg/controller/controller_test.go b/pkg/controller/controller_test.go index 193c756..f3f7a2c 100644 --- a/pkg/controller/controller_test.go +++ b/pkg/controller/controller_test.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -19,6 +19,7 @@ import ( "context" "crypto/tls" "encoding/json" + "fmt" "net" "net/http" "net/http/httptest" @@ -30,10 +31,12 @@ import ( athenz_domain "github.com/AthenZ/k8s-athenz-syncer/pkg/apis/athenz/v1" "github.com/AthenZ/k8s-athenz-syncer/pkg/client/clientset/versioned/fake" "github.com/AthenZ/k8s-athenz-syncer/pkg/cron" + "github.com/AthenZ/k8s-athenz-syncer/pkg/log" "github.com/AthenZ/k8s-athenz-syncer/pkg/util" "github.com/ardielle/ardielle-go/rdl" "github.com/google/go-cmp/cmp" k8sfake "k8s.io/client-go/kubernetes/fake" + "k8s.io/client-go/util/workqueue" ) const ( @@ -45,7 +48,7 @@ func newController() *Controller { athenzclientset := fake.NewSimpleClientset() clientset := k8sfake.NewSimpleClientset() zmsclient := zms.NewClient("https://zms.athenz.com", &http.Transport{}) - util := util.NewUtil("admin.domain", []string{"kube-system", "kube-public", "kube-test"}) + util := util.NewUtil("admin.domain", []string{"kube-system", "kube-public", "kube-test"}, []string{"acceptance-test"}) cm := &cron.AthenzContactTimeConfigMap{ Namespace: "kube-yahoo", Name: "athenzcall-config", @@ -161,7 +164,7 @@ func TestDeepCopy(t *testing.T) { } } -//TestZmsGetSignedDomains - test zms API call to get signed domains +// TestZmsGetSignedDomains - test zms API call to get signed domains func TestZmsGetSignedDomains(t *testing.T) { d := getFakeDomain() signedDomain := zms.SignedDomains{ @@ -204,3 +207,76 @@ func testingHTTPClient(handler http.Handler) (*http.Client, func()) { return cli, s.Close } +func TestNsinformerhandler(t *testing.T) { + log.InitLogger("/tmp/log/test.log", "info") + + tests := []struct { + name string + keyFunc func(obj interface{}) (string, error) + excludedNS []string + expectedResult string + expectedQueueLen int + shouldAddToQueue bool + }{ + { + name: "Error from key function", + keyFunc: func(obj interface{}) (string, error) { + return "", fmt.Errorf("mock key function error") + }, + excludedNS: []string{}, + expectedResult: "", + expectedQueueLen: 0, + shouldAddToQueue: false, + }, + { + name: "Excluded namespace", + keyFunc: func(obj interface{}) (string, error) { + return "test-excluded", nil + }, + excludedNS: []string{"test-excluded"}, + expectedResult: "test-excluded", + expectedQueueLen: 0, + shouldAddToQueue: false, + }, + { + name: "Non-excluded namespace gets added to queue", + keyFunc: func(obj interface{}) (string, error) { + return "test-namespace", nil + }, + excludedNS: []string{}, + expectedResult: "test-namespace", + expectedQueueLen: 1, + shouldAddToQueue: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := newController() + c.util = util.NewUtil("admin.domain", []string{"kube-system"}, tt.excludedNS) + mockQueue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter()) + c.queue = mockQueue + + result := c.nsinformerhandler(tt.keyFunc, &athenz_domain.AthenzDomain{}) + + if result != tt.expectedResult { + t.Errorf("Expected result '%s', got: '%s'", tt.expectedResult, result) + } + + // Sleep to allow item to be queued + time.Sleep(2 * time.Second) + + if mockQueue.Len() != tt.expectedQueueLen { + t.Errorf("Expected queue length to be %d, got %d", tt.expectedQueueLen, mockQueue.Len()) + } + + if tt.shouldAddToQueue && mockQueue.Len() > 0 { + expectedDomain := c.util.NamespaceToDomain(tt.expectedResult) + item, _ := mockQueue.Get() + if item != expectedDomain { + t.Errorf("Expected %s in queue, got %s", expectedDomain, item) + } + } + }) + } +} diff --git a/pkg/cron/cron.go b/pkg/cron/cron.go index d1029fe..d0b4ea8 100644 --- a/pkg/cron/cron.go +++ b/pkg/cron/cron.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -150,6 +150,10 @@ func (c *Cron) FullResync(stopCh <-chan struct{}) { log.Error("Error occurred when casting namespace into string") continue } + if c.util.IsNamespaceExcluded(namespace.ObjectMeta.Name) { + log.Infof("Skip processing excluded namespace: %s", namespace.ObjectMeta.Name) + continue + } domainName := c.util.NamespaceToDomain(namespace.ObjectMeta.Name) c.queue.AddRateLimited(domainName) } diff --git a/pkg/cron/cron_test.go b/pkg/cron/cron_test.go index 70c16cf..8bb3c5a 100644 --- a/pkg/cron/cron_test.go +++ b/pkg/cron/cron_test.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -48,7 +48,7 @@ func newCron() *Cron { clientset := k8sfake.NewSimpleClientset() rateLimiter := ratelimiter.NewRateLimiter(250 * time.Millisecond) queue := workqueue.NewRateLimitingQueue(rateLimiter) - util := util.NewUtil("test.domain", []string{"kube-system"}) + util := util.NewUtil("test.domain", []string{"kube-system"}, []string{"acceptance-test"}) athenzclientset := fake.NewSimpleClientset() informer := athenzInformer.NewAthenzDomainInformer(athenzclientset, 0, cache.Indexers{ "trustDomain": cr.TrustDomainIndexFunc, diff --git a/pkg/util/util.go b/pkg/util/util.go index 0960bdc..7004598 100644 --- a/pkg/util/util.go +++ b/pkg/util/util.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -23,15 +23,22 @@ import ( // Util - struct with 2 fields adminDomain and list of system namespaces type Util struct { - adminDomain string - systemNamespaces []string + adminDomain string + systemNamespaces []string + excludeNamespaces map[string]bool } // NewUtil - create new Util object -func NewUtil(adminDomain string, systemNamespaces []string) *Util { +func NewUtil(adminDomain string, systemNamespaces []string, excludeNamespaces []string) *Util { + excludedNamespaceMap := make(map[string]bool) + for _, ns := range excludeNamespaces { + excludedNamespaceMap[ns] = true + } + return &Util{ - adminDomain: adminDomain, - systemNamespaces: systemNamespaces, + adminDomain: adminDomain, + systemNamespaces: systemNamespaces, + excludeNamespaces: excludedNamespaceMap, } } @@ -78,6 +85,14 @@ func (u *Util) IsSystemDomain(domain string) bool { return false } +// IsNamespaceExcluded - check if the current namespace is in the skip namespaces list +func (u *Util) IsNamespaceExcluded(ns string) bool { + if _, ok := u.excludeNamespaces[ns]; ok { + return true + } + return false +} + // IsAdminDomain - check if domain is admin domain func (u *Util) IsAdminDomain(domain string) bool { if domain == u.adminDomain { diff --git a/pkg/util/util_test.go b/pkg/util/util_test.go index f4fbdc2..3e50f84 100644 --- a/pkg/util/util_test.go +++ b/pkg/util/util_test.go @@ -5,7 +5,7 @@ Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at - http://www.apache.org/licenses/LICENSE-2.0 + http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, @@ -20,7 +20,7 @@ import ( ) func newUtil() *Util { - return NewUtil("admin.domain", []string{"kube-system", "kube-public", "kube-test"}) + return NewUtil("admin.domain", []string{"kube-system", "kube-public", "kube-test"}, []string{"acceptance-test"}) } // TestNamespaceConversion - test conversion function @@ -113,3 +113,18 @@ func TestGetSystemNSDomains(t *testing.T) { } } } + +// TestIsNamespaceExcluded - test whether certain namespace is in the skip namespaces list +func TestIsNamespaceExcluded(t *testing.T) { + u := newUtil() + test1 := "acceptance-test" + result1 := u.IsNamespaceExcluded(test1) + if !result1 { + t.Error("test1 failed. acceptance-test is in the skip namespaces list!") + } + test2 := "kube-system" + result2 := u.IsNamespaceExcluded(test2) + if result2 { + t.Error("test2 failed. kube-system is not in the skip namespaces list!") + } +}