From 0182cc561dad7e3025834184eb9d12e99d9b4875 Mon Sep 17 00:00:00 2001 From: Benjamin Pineau Date: Sun, 19 Jan 2020 17:49:13 +0100 Subject: [PATCH] Support filtering by namespace --- README.md | 6 +++--- assets/katafygio.yaml | 3 +++ cmd/execute.go | 5 +++-- cmd/flags.go | 7 ++++++- pkg/observer/observer.go | 12 +++++++++++- pkg/observer/observer_test.go | 25 ++++++++++++++++++++++--- 6 files changed, 48 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 3088607..cf0ae11 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,7 @@ Flags: -v, --log-level string Log level (default "info") -o, --log-output string Log output (default "stderr") -r, --log-server string Log server (if using syslog) + -a, --namespace string Only dump objects from this namespace -n, --no-git Don't version with git -i, --resync-interval int Full resync interval in seconds (0 to disable) (default 900) ``` @@ -84,8 +85,7 @@ Flags: All settings can be passed by command line options, or environment variable, or in [a yaml configuration file](https://github.com/bpineau/katafygio/blob/master/assets/katafygio.yaml) -(thanks to Viper and Cobra libs). The environment are the same as cli options, -in uppercase, prefixed by "KF", and with underscore instead of dashs. ie.: +The environment are the same as command line options, in uppercase, prefixed by "KF_", and with underscore instead of dashs. ie.: ``` export KF_GIT_URL=https://user:token@github.com/myorg/myrepos.git @@ -100,7 +100,7 @@ export KUBECONFIG=/tmp/kconfig ## Installation You can find pre-built binaries in the [releases](https://github.com/bpineau/katafygio/releases) page, -ready to run on your desktop or in a cluster. +ready to run on your desktop or in a Kubernetes cluster. We also provide a [docker image](https://hub.docker.com/r/bpineau/katafygio/). diff --git a/assets/katafygio.yaml b/assets/katafygio.yaml index fb8ad61..afa3285 100644 --- a/assets/katafygio.yaml +++ b/assets/katafygio.yaml @@ -45,6 +45,9 @@ resync-interval: 900 # - configmap:kube-system/datadog-leader-elector # - deployment:default/testdeploy +# Only dump objects belonging to a specific namespace +#namespace: + # Set to true o dump once and exit (instead of continuously dumping new changes) dump-only: false diff --git a/cmd/execute.go b/cmd/execute.go index 18640bb..fb75aee 100644 --- a/cmd/execute.go +++ b/cmd/execute.go @@ -31,7 +31,8 @@ var ( Use: appName, Short: "Backup Kubernetes cluster as yaml files", Long: "Backup Kubernetes cluster as yaml files in a git repository.\n" + - "--exclude-kind (-x) and --exclude-object (-y) may be specified several times.", + "--exclude-kind (-x) and --exclude-object (-y) may be specified several times,\n" + + "or once with several comma separated values.", SilenceUsage: true, SilenceErrors: true, PreRun: bindConf, @@ -71,7 +72,7 @@ func runE(cmd *cobra.Command, args []string) (err error) { evts := event.New() fact := controller.NewFactory(logger, filter, resyncInt, exclobj) reco := recorder.New(logger, evts, localDir, resyncInt*2, dryRun).Start() - obsv := observer.New(logger, restcfg, evts, fact, exclkind).Start() + obsv := observer.New(logger, restcfg, evts, fact, exclkind, namespace).Start() logger.Info(appName, " started") sigterm := make(chan os.Signal, 1) diff --git a/cmd/flags.go b/cmd/flags.go index 781b1ab..a662d42 100644 --- a/cmd/flags.go +++ b/cmd/flags.go @@ -12,6 +12,7 @@ var ( cfgFile string apiServer string context string + namespace string kubeConf string dryRun bool dumpMode bool @@ -48,6 +49,9 @@ func init() { RootCmd.PersistentFlags().StringVarP(&context, "context", "q", "", "Kubernetes configuration context") bindPFlag("context", "context") + RootCmd.PersistentFlags().StringVarP(&namespace, "namespace", "a", "", "Only dump objects from this namespace") + bindPFlag("namespace", "namespace") + RootCmd.PersistentFlags().StringVarP(&kubeConf, "kube-config", "k", "", "Kubernetes configuration path") bindPFlag("kube-config", "kube-config") @@ -81,7 +85,7 @@ func init() { RootCmd.PersistentFlags().StringSliceVarP(&exclobj, "exclude-object", "y", nil, "Object to exclude. Eg. 'configmap:kube-system/kube-dns'") bindPFlag("exclude-object", "exclude-object") - RootCmd.PersistentFlags().StringVarP(&filter, "filter", "l", "", "Label filter. Select only objects matching the label.") + RootCmd.PersistentFlags().StringVarP(&filter, "filter", "l", "", "Label filter. Select only objects matching the label") bindPFlag("filter", "filter") RootCmd.PersistentFlags().IntVarP(&healthP, "healthcheck-port", "p", 0, "Port for answering healthchecks on /health url") @@ -98,6 +102,7 @@ func init() { func bindConf(cmd *cobra.Command, args []string) { apiServer = viper.GetString("api-server") context = viper.GetString("context") + namespace = viper.GetString("namespace") kubeConf = viper.GetString("kube-config") dryRun = viper.GetBool("dry-run") dumpMode = viper.GetBool("dump-only") diff --git a/pkg/observer/observer.go b/pkg/observer/observer.go index 6bd8bea..fa18993 100644 --- a/pkg/observer/observer.go +++ b/pkg/observer/observer.go @@ -52,6 +52,7 @@ type Observer struct { factory ControllerFactory logger logger excludedkind []string + namespace string } type gvk struct { @@ -62,7 +63,7 @@ type gvk struct { type resources map[string]*gvk // New returns a new observer, that will watch API resources and create controllers -func New(log logger, client restclient, notif event.Notifier, factory ControllerFactory, excluded []string) *Observer { +func New(log logger, client restclient, notif event.Notifier, factory ControllerFactory, excluded []string, namespace string) *Observer { return &Observer{ notifier: notif, discovery: discovery.NewDiscoveryClientForConfigOrDie(client.GetRestConfig()), @@ -71,6 +72,7 @@ func New(log logger, client restclient, notif event.Notifier, factory Controller factory: factory, logger: log, excludedkind: excluded, + namespace: namespace, } } @@ -140,6 +142,9 @@ func (c *Observer) refresh() error { cname := strings.ToLower(res.apiResource.Kind) namespace := metav1.NamespaceAll + if c.namespace != "" { + namespace = c.namespace + } lw := &cache.ListWatch{ ListFunc: func(options metav1.ListOptions) (runtime.Object, error) { return c.cpool.Resource(resource).Namespace(namespace).List(options) @@ -189,6 +194,11 @@ func (c *Observer) expandAndFilterAPIResources(groups []*metav1.APIResourceList) continue } + // ignore non namespaced resources, when we have a namespace filter + if c.namespace != "" && !ar.Namespaced { + continue + } + // only consider resources that are getable, listable an watchable if !isSubList(ar.Verbs, []string{"list", "get", "watch"}) { continue diff --git a/pkg/observer/observer_test.go b/pkg/observer/observer_test.go index 97f24ff..95a671a 100644 --- a/pkg/observer/observer_test.go +++ b/pkg/observer/observer_test.go @@ -62,6 +62,7 @@ type resTest struct { resources []*metav1.APIResourceList exclude []string expect []string + namespace string } var resourcesTests = []resTest{ @@ -167,12 +168,30 @@ var resourcesTests = []resTest{ }, }, }, + + { + title: "Eliminate non namespaced", + exclude: []string{}, + expect: []string{"bar1", "bar2"}, + namespace: "foo", + resources: []*metav1.APIResourceList{ + { + GroupVersion: "foo/v42", + APIResources: []metav1.APIResource{ + {Name: "bar1", Namespaced: true, Kind: "Bar1", Verbs: stdVerbs}, + {Name: "bar2", Namespaced: true, Kind: "Bar2", Verbs: stdVerbs}, + {Name: "bar3", Namespaced: false, Kind: "Bar3", Verbs: stdVerbs}, + {Name: "bar4", Namespaced: false, Kind: "Bar4", Verbs: stdVerbs}, + }, + }, + }, + }, } func TestObserver(t *testing.T) { for _, tt := range resourcesTests { factory := new(mockFactory) - obs := New(new(mockLog), new(mockClient), &mockNotifier{}, factory, tt.exclude) + obs := New(new(mockLog), new(mockClient), &mockNotifier{}, factory, tt.exclude, tt.namespace) client := fakeclientset.NewSimpleClientset() fakeDiscovery, _ := client.Discovery().(*fakediscovery.FakeDiscovery) @@ -213,7 +232,7 @@ func TestObserverDuplicas(t *testing.T) { fakeDiscovery.Resources = duplicatesTest factory := new(mockFactory) - obs := New(new(mockLog), new(mockClient), &mockNotifier{}, factory, make([]string, 0)) + obs := New(new(mockLog), new(mockClient), &mockNotifier{}, factory, make([]string, 0), "") obs.discovery = fakeDiscovery obs.Start() err := obs.refresh() @@ -243,7 +262,7 @@ func TestObserverRecoverFromDicoveryFailure(t *testing.T) { } factory := new(mockFactory) - obs := New(new(mockLog), new(mockClient), &mockNotifier{}, factory, make([]string, 0)) + obs := New(new(mockLog), new(mockClient), &mockNotifier{}, factory, make([]string, 0), "") // failing discovery obs.discovery.RESTClient().(*rest.RESTClient).Client = fakeClient.Client