diff --git a/README.md b/README.md index a6697d3..2fc260f 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,7 @@ You can use `--include` or `-i` to control which resources to include in the kfi | group, g | apiVersion | rbac.authorization.k8s.io | | version, v | apiVersion | v1 | | namespace, ns | metadata.namespace | kube-system | +| labels, l | metadata.labels | app=my-app | #### Examples @@ -130,6 +131,12 @@ You can use multiple `--include` flags. kfilt will output resources that match a kfilt -f ./pkg/decoder/test.yaml -i k=serviceaccount -i k=configmap ``` +##### Filter with Label Selectors + +``` +kfilt -f ./pkg/decoder/test.yaml -i labels=app=test +``` + ### Excluding Resources The `--exclude` or `-x` flag will allow you to exclude resources. This supports the same key value pairs as the `--include` flag. @@ -154,14 +161,22 @@ kfilt -f ./pkg/decoder/test.yaml -x name=test kfilt -f ./pkg/decoder/test.yaml -x kind=configmap -x k=serviceaccount ``` +##### Exclude with Label Selectors + +``` +kfilt -f ./pkg/decoder/test.yaml -x labels=app=test +``` + ### Shortcuts -Because "kind" and "name" are the most commonly used fields to filter by, kfilt has special flags allowing you to save some typing. +Because "kind", "name", and "labels" are the most commonly used fields to filter by, kfilt has special flags allowing you to save some typing. You can include by "kind" by using the `--kind` (or `-k`) flag with just the name of the kind you want to filter by. You can use `--exclude-kind` (or `-K`) for exclusions. The corresponding flags for "name" queries are `--name` (`-n`) and `--exclude-name` (`-N`). +Finally, you can use label selectors with the `--labels` (`-l`) and `--exclude-labels` (`L`) flags. + #### Include ConfigMaps and Service Accounts ``` @@ -174,6 +189,12 @@ kfilt -f ./pkg/decoder/test.yaml -k configmap -k serviceaccount kfilt -f ./pkg/decoder/test.yaml -N test ``` +#### Include resources labeled with "app=test" + +``` +kfilt -f ./pkg/decoder/test.yaml -l app=test +``` + --- 1 *note*: kfilt has not been tested extensively on Windows. Please file an issue if you run into any problems. diff --git a/cmd/root.go b/cmd/root.go index 9393556..d354812 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -20,13 +20,15 @@ var ( ) type root struct { - includeKinds []string - includeNames []string - excludeKinds []string - excludeNames []string - include []string - exclude []string - filename string + includeKinds []string + includeNames []string + excludeKinds []string + excludeNames []string + includeLabelSelector []string + excludeLabelSelector []string + include []string + exclude []string + filename string } func newRootCommand(args []string) *cobra.Command { @@ -50,6 +52,8 @@ func newRootCommand(args []string) *cobra.Command { rootCmd.Flags().StringSliceVarP(&root.includeNames, "name", "n", []string{}, "Only include resources with name") rootCmd.Flags().StringSliceVarP(&root.excludeKinds, "exclude-kind", "K", []string{}, "Exclude resources of kind") rootCmd.Flags().StringSliceVarP(&root.excludeNames, "exclude-name", "N", []string{}, "Exclude resources with name") + rootCmd.Flags().StringSliceVarP(&root.includeLabelSelector, "labels", "l", []string{}, "Only include resources matching the label selector") + rootCmd.Flags().StringSliceVarP(&root.excludeLabelSelector, "exclude-labels", "L", []string{}, "Exclude resources matching the label selector") rootCmd.Flags().StringArrayVarP(&root.include, "include", "i", []string{}, "Include resources matching criteria") rootCmd.Flags().StringArrayVarP(&root.exclude, "exclude", "x", []string{}, "Exclude resources matching criteria") rootCmd.Flags().StringVarP(&root.filename, "filename", "f", "", "Read manifests from file or URL") @@ -87,6 +91,10 @@ func (r *root) run() error { kfilt.AddInclude(filter.Matcher{Name: n}) } + for _, l := range r.includeLabelSelector { + kfilt.AddInclude(filter.Matcher{LabelSelector: l}) + } + for _, k := range r.excludeKinds { kfilt.AddExclude(filter.Matcher{Kind: k}) } @@ -95,6 +103,10 @@ func (r *root) run() error { kfilt.AddExclude(filter.Matcher{Name: n}) } + for _, l := range r.excludeLabelSelector { + kfilt.AddExclude(filter.Matcher{LabelSelector: l}) + } + for _, q := range r.include { if q != "" { s, err := filter.NewMatcher(q) @@ -115,7 +127,10 @@ func (r *root) run() error { } } - filtered := kfilt.Filter(results) + filtered, err := kfilt.Filter(results) + if err != nil { + return err + } // print if err := printer.New().Print(filtered); err != nil { diff --git a/go.mod b/go.mod index c18e061..5096ce7 100644 --- a/go.mod +++ b/go.mod @@ -3,15 +3,10 @@ module github.com/ryane/kfilt go 1.12 require ( - github.com/gogo/protobuf v1.2.1 // indirect - github.com/google/gofuzz v1.0.0 // indirect github.com/pkg/errors v0.8.1 github.com/spf13/cobra v0.0.2 - github.com/spf13/pflag v1.0.3 // indirect - golang.org/x/net v0.0.0-20190620200207-3b0461eec859 // indirect - gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.2.2 k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1 - k8s.io/klog v0.3.3 // indirect - sigs.k8s.io/yaml v1.1.0 // indirect + sigs.k8s.io/kustomize/v3 v3.1.0 + sigs.k8s.io/yaml v1.1.0 ) diff --git a/go.sum b/go.sum index ca85001..a4656ac 100644 --- a/go.sum +++ b/go.sum @@ -1,110 +1,139 @@ -github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= -github.com/armon/consul-api v0.0.0-20180202201655-eb2c6b5be1b6/go.mod h1:grANhF5doyWs3UAsr3K4I6qtAmlQcZDesFNEHPZAzj8= -github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE= -github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk= -github.com/coreos/go-semver v0.2.0/go.mod h1:nnelYz7RCh+5ahJtPPxZlU+153eP4D4r3EedlOD2RNk= -github.com/cpuguy83/go-md2man v1.0.10/go.mod h1:SmD6nW6nTyfqj6ABTjUi3V3JVMnlJmwcJI5acqYI6dE= +github.com/NYTimes/gziphandler v0.0.0-20170623195520-56545f4a5d46/go.mod h1:3wb06e3pkSAbeQ52E9H9iFoQsEEwGN64994WTCIhntQ= +github.com/PuerkitoBio/purell v1.0.0/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20160726150825-5bd2802263f2/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/davecgh/go-spew v0.0.0-20151105211317-5215b55f46b2/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/docker/spdystream v0.0.0-20160310174837-449fdfce4d96/go.mod h1:Qh8CwZgvJUkLughtfhJv5dyTYa91l1fOUCrgjqmcifM= -github.com/elazarl/goproxy v0.0.0-20170405201442-c4fc26588b6e/go.mod h1:/Zj4wYkgs4iZTTu3o/KG3Itv/qCCa8VVMlb3i9OVuzc= -github.com/evanphx/json-patch v0.0.0-20190203023257-5858425f7550/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/emicklei/go-restful v0.0.0-20170410110728-ff4f55a20633/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/emicklei/go-restful v2.9.6+incompatible/go.mod h1:otzb+WCGbkyDHkqmQmT5YD2WR4BBwUdeQoFo8l/7tVs= +github.com/evanphx/json-patch v4.5.0+incompatible h1:ouOWdg56aJriqS0huScTkVXPC5IcNrDCXZ6OoTAWu7M= +github.com/evanphx/json-patch v4.5.0+incompatible/go.mod h1:50XU6AFN0ol/bzJsmQLiYLvXMP4fmwYFNcr97nuDLSk= +github.com/fsnotify/fsnotify v1.4.7 h1:IXs+QLmnXW2CcXuY+8Mzv/fWEsPGWxqefPtCP5CnV9I= github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= -github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415 h1:WSBJMqJbLxsn+bTCPyPYZfqHdJmc8MK4wrBjMft6BAM= -github.com/gogo/protobuf v0.0.0-20171007142547-342cbe0a0415/go.mod h1:r8qH/GZQm5c6nD/R0oafs1akxWv10x8SbQlK7atdtwQ= +github.com/ghodss/yaml v0.0.0-20150909031657-73d445a93680/go.mod h1:4dBDuWmgqj2HViK6kFavaiC9ZROes6MMH2rRYeMEF04= +github.com/go-openapi/jsonpointer v0.0.0-20160704185906-46af16f9f7b1/go.mod h1:+35s3my2LFTysnkMfxsJBAMHj/DoqoB9knIWoYG/Vk0= +github.com/go-openapi/jsonpointer v0.19.2/go.mod h1:3akKfEdA7DF1sugOqz1dVQHBcuDBPKZGEoHC/NkiQRg= +github.com/go-openapi/jsonreference v0.0.0-20160704190145-13c6e3589ad9/go.mod h1:W3Z9FmVs9qj+KR4zFKmDPGiLdk1D9Rlm7cyMvf57TTg= +github.com/go-openapi/jsonreference v0.19.2/go.mod h1:jMjeRr2HHw6nAVajTXJ4eiUwohSTlpa0o73RUL1owJc= +github.com/go-openapi/spec v0.0.0-20160808142527-6aced65f8501/go.mod h1:J8+jY1nAiCcj+friV/PDoE1/3eeccG9LYBs0tYvLOWc= +github.com/go-openapi/spec v0.19.2/go.mod h1:sCxk3jxKgioEJikev4fgkNmwS+3kuYdJtcsZsD5zxMY= +github.com/go-openapi/swag v0.0.0-20160704191624-1d0bd113de87/go.mod h1:DXUve3Dpr1UfpPtxFw+EFuQ41HhCWZfha5jSVRG7C7I= +github.com/go-openapi/swag v0.19.2/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= github.com/gogo/protobuf v1.2.1 h1:/s5zKNz0uPFCZ5hddgPdo2TK2TVrUNMn0OOX8/aZMTE= github.com/gogo/protobuf v1.2.1/go.mod h1:hp+jE20tsWTFYpLwKvXlhS1hjn+gTNwPg2I6zVXpSg4= -github.com/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/protobuf v0.0.0-20161109072736-4bd1920723d7/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= -github.com/google/go-cmp v0.3.0 h1:crn/baboCvb5fXaQ0IJ1SGTsTVrWpDsCWC8EGETZijY= -github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf h1:+RRA9JqSOZFfKrOeqr2z77+8R2RKyh8PG66dcu1V0ck= -github.com/google/gofuzz v0.0.0-20170612174753-24818f796faf/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= +github.com/golang/protobuf v1.3.1 h1:YF8+flBXS5eO826T4nzqPrxfhQThhXl0YzfuUPu4SBg= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/google/gofuzz v0.0.0-20161122191042-44d81051d367/go.mod h1:HP5RmnzzSNb993RKQDq4+1A4ia9nllfqcQFTQJedwGI= github.com/google/gofuzz v1.0.0 h1:A8PeW59pxE9IoFRqBp37U+mSNaQoZ46F1f0f863XSXw= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= -github.com/google/uuid v1.0.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/googleapis/gnostic v0.0.0-20170729233727-0c5108395e2d/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= -github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= -github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/googleapis/gnostic v0.0.0-20170426233943-68f4ded48ba9/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/googleapis/gnostic v0.3.0 h1:CcQijm0XKekKjP/YCz28LXVSpgguuB+nCxaSjCe09y0= +github.com/googleapis/gnostic v0.3.0/go.mod h1:sJBsCZ4ayReDTBIg8b9dl28c5xFWyhBTVRp3pOg5EKY= +github.com/hpcloud/tail v1.0.0 h1:nfCOvKYfkgYP8hkirhJocXT2+zOD8yUNjXaWfTlyFKI= github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= -github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be h1:AHimNtVIpiBjPUhEF5KNCkrUyqTSA5zWUl8sQ2bfGBE= -github.com/json-iterator/go v0.0.0-20180701071628-ab8a2e0c74be/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v0.0.0-20180612202835-f2b4162afba3/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= +github.com/json-iterator/go v1.1.6 h1:MrUvLMLTMxbqFJ9kzlvat/rYZqZnW3u4wkLzWTaFwKs= +github.com/json-iterator/go v1.1.6/go.mod h1:+SdeFBvtyEkXs7REEP0seUULqWtbJapLOCVDaaPEHmU= github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q= github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= -github.com/magiconair/properties v1.8.0/go.mod h1:PppfXfuXeibc/6YijjN8zIbojt8czPbwD3XqdrwzmxQ= -github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.5/go.mod h1:9r2w37qlBe7rQ6e1fg1S/9xpWHSnaqNdHD3WcMdbPDA= +github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mailru/easyjson v0.0.0-20160728113105-d5b7844b561a/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190620125010-da37f6c1e481/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180320133207-05fbef0ca5da/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= -github.com/mxk/go-flowrate v0.0.0-20140419014527-cca7078d478f/go.mod h1:ZdcZmHo+o7JKHSa8/e818NopupXU1YMK5fe1lsApnBw= +github.com/munnerz/goautoneg v0.0.0-20120707110453-a547fc61f48d/go.mod h1:+n7T8mK8HuQTcFwEeznm/DIxMOiR9yIdICNftLE1DvQ= +github.com/onsi/ginkgo v0.0.0-20170829012221-11459a886d9c/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= -github.com/onsi/gomega v0.0.0-20190113212917-5533ce8a0da3/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= -github.com/pelletier/go-toml v1.2.0/go.mod h1:5z9KED0ma1S8pY6P1sdut58dfprrGBbd/94hg7ilaic= +github.com/onsi/ginkgo v1.8.0 h1:VkHVNpR4iVnU8XQR6DBm8BqYjN7CRzw+xKUbVVbbW9w= +github.com/onsi/ginkgo v1.8.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/gomega v0.0.0-20170829124025-dcabb60a477c/go.mod h1:C1qb7wdrVGGVU+Z6iS04AVkA3Q65CEZX59MT0QO5uiA= +github.com/onsi/gomega v1.5.0 h1:izbySO9zDPmjJ8rDjLvkA2zJHIo+HkYXHnf7eN7SSyo= +github.com/onsi/gomega v1.5.0/go.mod h1:ex+gbHU/CVuBBDIJjb2X0qEXbFg53c61hWP/1CpauHY= github.com/pkg/errors v0.8.1 h1:iURUrRGxPUNPdy5/HRSm+Yj6okJ6UtLINN0Q9M4+h3I= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v0.0.0-20151028094244-d8ed2627bdf0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/russross/blackfriday v1.5.2/go.mod h1:JO/DiYxRf+HjHt06OyowR9PTA263kcR/rfWxYHBV53g= -github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ= -github.com/spf13/cast v1.3.0/go.mod h1:Qx5cxh0v+4UWYiBimWS+eyWzqEqokIECu5etghLkUJE= github.com/spf13/cobra v0.0.2 h1:NfkwRbgViGoyjBKsLI0QMDcuMnhM+SBg3T0cGfpvKDE= github.com/spf13/cobra v0.0.2/go.mod h1:1l0Ry5zgKvJasoi3XT1TypsSe7PqH0Sj9dhYf7v3XqQ= -github.com/spf13/cobra v0.0.5 h1:f0B+LkLX6DtmRH1isoNA9VTtNUK9K8xYd28JNNfOv/s= -github.com/spf13/cobra v0.0.5/go.mod h1:3K3wKZymM7VvHMDS9+Akkh4K60UwM26emMESw8tLCHU= -github.com/spf13/jwalterweatherman v1.0.0/go.mod h1:cQK4TGJAtQXfYWX+Ddv3mKDzgVb68N+wFjFa4jdeBTo= -github.com/spf13/pflag v1.0.1/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= +github.com/spf13/pflag v0.0.0-20170130214245-9ff6c6923cff/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= github.com/spf13/pflag v1.0.3 h1:zPAT6CGy6wXeQ7NtTnaTerfKOsV6V6F8agHXFiazDkg= github.com/spf13/pflag v1.0.3/go.mod h1:DYY7MBk1bdzusC3SYhjObp+wFpr4gzcvqqNjLnInEg4= -github.com/spf13/viper v1.3.2/go.mod h1:ZiWeW+zYFKm7srdB9IoDzzZXaJaI5eL9QjNiN/DMA2s= -github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w= -github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= -github.com/ugorji/go/codec v0.0.0-20181204163529-d75b2dcb6bc8/go.mod h1:VFNgLljTbGfSG7qAOspJ7OScBnGdDN/yBr0sguwnwf0= -github.com/xordataexchange/crypt v0.0.3-0.20170626215501-b2862e3d0a77/go.mod h1:aYKd//L2LvnjZzWKhF00oedf4jCCReLcmhLdhm1A27Q= -golang.org/x/crypto v0.0.0-20181203042331-505ab145d0a9/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v0.0.0-20151208002404-e3a8ff8ce365/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0 h1:TivCn/peBQ7UY8ooIcPgZFpTNSz0Q2U6UrFlUfqbe0Q= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190611184440-5c40567a22f8/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/net v0.0.0-20170114055629-f2499483f923/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= -golang.org/x/net v0.0.0-20190206173232-65e2d4e15006 h1:bfLnR+k0tq5Lqt6dflRLcZiz6UaXCMt3vhYJ1l4FQ80= -golang.org/x/net v0.0.0-20190206173232-65e2d4e15006/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190613194153-d28f0bde5980/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20190620200207-3b0461eec859 h1:R/3boaszxrf1GEUWTVDzSKVwLmSJpwZ1yqXm8j0v2QI= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7 h1:rTIdg5QFRR7XCaK4LCjBiPbx8j4DQRpdYMnGn/bJUEU= -golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= -golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20170830134202-bb24a47a89ea/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20181205085412-a5c9d58dba9a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= -golang.org/x/sys v0.0.0-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc= -golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190616124812-15dcb6c0061f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190621203818-d432491b9138 h1:t8BZD9RDjkm9/h7yYN6kE8oaeov5r9aztkB7zKA5Tkg= +golang.org/x/sys v0.0.0-20190621203818-d432491b9138/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/text v0.0.0-20160726164857-2910a502d2bf/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0 h1:g61tztE5qeGQ89tm6NTjjM9VPIm088od1l6aSorWRWg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db h1:6/JqlYfC1CCaLnGceQTI+sDGhC9UBSPAsBqI0Gun6kU= -golang.org/x/text v0.3.1-0.20181227161524-e6919f6577db/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.2 h1:tW2bmiBqwgJj/UpqtC8EpXEZVYOwU0yG4iWbprSVAcs= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/tools v0.0.0-20180221164845-07fd8470d635/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20181011042414-1f849cf54d09/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190614205625-5aca471b1d59/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/fsnotify.v1 v1.4.7 h1:xOHLXZwVvI9hhs+cLKq5+I5onOuwQLhQwiu63xxlHs4= gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= -gopkg.in/inf.v0 v0.9.0 h1:3zYtXIO92bvsdS3ggAdA8Gb4Azj0YU+TVY1uGYNFA8o= -gopkg.in/inf.v0 v0.9.0/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= gopkg.in/inf.v0 v0.9.1 h1:73M5CoZyi3ZLMOyDlQh031Cx6N9NDJ2Vvfl76EDAgDc= gopkg.in/inf.v0 v0.9.1/go.mod h1:cWUDdTG/fYaXco+Dcufb5Vnc6Gp2YChqWtbxRZE0mXw= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= gopkg.in/yaml.v2 v2.2.1/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +k8s.io/api v0.0.0-20190313235455-40a48860b5ab h1:DG9A67baNpoeweOy2spF1OWHhnVY5KR7/Ek/+U1lVZc= +k8s.io/api v0.0.0-20190313235455-40a48860b5ab/go.mod h1:iuAfoD4hCxJ8Onx9kaTIt30j7jUFS00AXQi6QMi99vA= k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1 h1:IS7K02iBkQXpCeieSiyJjGoLSdVOv2DbPaWHJ+ZtgKg= k8s.io/apimachinery v0.0.0-20190313205120-d7deff9243b1/go.mod h1:ccL7Eh7zubPUSh9A3USN90/OzHNSVN6zxzde07TDCL0= -k8s.io/apimachinery v0.0.0-20190612125636-6a5db36e93ad h1:x1lITOfDEbnzt8D1cZJsPbdnx/hnv28FxY2GKkxmxgU= -k8s.io/apimachinery v0.0.0-20190612125636-6a5db36e93ad/go.mod h1:I4A+glKBHiTgiEjQiCCQfCAIcIMFGt291SmsvcrFzJA= -k8s.io/klog v0.3.1 h1:RVgyDHY/kFKtLqh67NvEWIgkMneNoIrdkN0CxDSQc68= -k8s.io/klog v0.3.1/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= +k8s.io/client-go v11.0.0+incompatible h1:LBbX2+lOwY9flffWlJM7f1Ct8V2SRNiMRDFeiwnJo9o= +k8s.io/client-go v11.0.0+incompatible/go.mod h1:7vJpHMYJwNQCWgzmNV+VYUl1zCObLyodBc8nIyt8L5s= +k8s.io/gengo v0.0.0-20190128074634-0689ccc1d7d6/go.mod h1:ezvh/TsK7cY6rbqRK0oQQ8IAqLxYwwyPxAX1Pzy0ii0= +k8s.io/klog v0.0.0-20181102134211-b9b56d5dfc92/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= k8s.io/klog v0.3.3 h1:niceAagH1tzskmaie/icWd7ci1wbG7Bf2c6YGcQv+3c= k8s.io/klog v0.3.3/go.mod h1:Gq+BEi5rUBO/HRz0bTSXDUcqjScdoY3a9IHpCEIOOfk= -k8s.io/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208 h1:5sW+fEHvlJI3Ngolx30CmubFulwH28DhKjGf70Xmtco= +k8s.io/kube-openapi v0.0.0-20190603182131-db7b694dc208/go.mod h1:nfDlWeOsu3pUf4yWGL+ERqohP4YsZcBJXWMK+gkzOA4= +sigs.k8s.io/kustomize/v3 v3.1.0 h1:FnNC1UtUjZlepvWUGwaAcFHw2rjNIaZvBUPCvaXz0Fo= +sigs.k8s.io/kustomize/v3 v3.1.0/go.mod h1:ztX4zYc/QIww3gSripwF7TBOarBTm5BvyAMem0kCzOE= +sigs.k8s.io/structured-merge-diff v0.0.0-20190525122527-15d366b2352e/go.mod h1:wWxsB5ozmmv/SG7nM11ayaAW51xMvak/t1r0CSlcokI= sigs.k8s.io/yaml v1.1.0 h1:4A07+ZFc2wgJwo8YNlQpr1rVlgUDlxXHhPJciaPY5gs= sigs.k8s.io/yaml v1.1.0/go.mod h1:UJmg0vDUVViEyp3mgSv9WPwZCDxu4rQW1olrI1uml+o= diff --git a/pkg/decoder/test.yaml b/pkg/decoder/test.yaml index a54bcf7..afc0de9 100644 --- a/pkg/decoder/test.yaml +++ b/pkg/decoder/test.yaml @@ -2,16 +2,22 @@ apiVersion: v1 kind: ServiceAccount metadata: name: test + labels: + app: test --- apiVersion: v1 kind: ServiceAccount metadata: name: test2 + labels: + app: test2 --- apiVersion: v1 kind: ConfigMap metadata: name: example-config + labels: + app: test data: key: val --- diff --git a/pkg/filter/errors.go b/pkg/filter/errors.go index 3c32249..b0e941f 100644 --- a/pkg/filter/errors.go +++ b/pkg/filter/errors.go @@ -19,6 +19,7 @@ type matcherParseError interface { MatcherParseError() bool } +// IsMatcherParseError indicates whether an error is a matcher parser error func IsMatcherParseError(err error) bool { te, ok := err.(matcherParseError) return ok && te.MatcherParseError() diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go index 4d8e07c..91242cc 100644 --- a/pkg/filter/filter.go +++ b/pkg/filter/filter.go @@ -4,11 +4,13 @@ import ( "github.com/ryane/kfilt/pkg/resource" ) +// Filter contains slices of inclusion and exclusion matchers type Filter struct { Include []Matcher Exclude []Matcher } +// New creates a new Filter func New() *Filter { return &Filter{ Include: []Matcher{}, @@ -16,12 +18,17 @@ func New() *Filter { } } -func (f *Filter) Filter(resources []resource.Resource) []resource.Resource { +// Filter returns a filtered slice of Resources +func (f *Filter) Filter(resources []resource.Resource) ([]resource.Resource, error) { + var err error filtered := append([]resource.Resource{}, resources...) // excludes for _, matcher := range f.Exclude { - filtered = exclude(filtered, matcher) + filtered, err = exclude(filtered, matcher) + if err != nil { + return filtered, err + } } // includes @@ -29,7 +36,11 @@ func (f *Filter) Filter(resources []resource.Resource) []resource.Resource { includeMap := make(map[string]interface{}) included := []resource.Resource{} for _, matcher := range f.Include { - for _, match := range filter(filtered, matcher) { + results, err := filter(filtered, matcher) + if err != nil { + return filtered, err + } + for _, match := range results { matchID := match.ID() if _, ok := includeMap[matchID]; !ok { includeMap[matchID] = struct{}{} @@ -40,33 +51,44 @@ func (f *Filter) Filter(resources []resource.Resource) []resource.Resource { filtered = included } - return filtered + return filtered, nil } +// AddInclude adds an inclusion matcher func (f *Filter) AddInclude(s Matcher) { f.Include = append(f.Include, s) } +// AddExclude adds an inclusion matcher func (f *Filter) AddExclude(s Matcher) { f.Exclude = append(f.Exclude, s) } -func filter(resources []resource.Resource, matcher Matcher) []resource.Resource { +func filter(resources []resource.Resource, matcher Matcher) ([]resource.Resource, error) { filtered := []resource.Resource{} for _, r := range resources { - if matcher.Match(r) { + ok, err := matcher.Match(r) + if err != nil { + return filtered, err + } + if ok { filtered = append(filtered, r) } } - return filtered + return filtered, nil } -func exclude(resources []resource.Resource, matcher Matcher) []resource.Resource { +func exclude(resources []resource.Resource, matcher Matcher) ([]resource.Resource, error) { filtered := []resource.Resource{} for _, r := range resources { - if !matcher.Match(r) { + + ok, err := matcher.Match(r) + if err != nil { + return filtered, err + } + if !ok { filtered = append(filtered, r) } } - return filtered + return filtered, nil } diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go index fa6c0c1..e450ea6 100644 --- a/pkg/filter/filter_test.go +++ b/pkg/filter/filter_test.go @@ -12,13 +12,16 @@ type includeMatchers []filter.Matcher type expectIDs []string func TestFilter(t *testing.T) { + var noError = func(err error) bool { return err == nil } tests := []struct { + name string exclude excludeMatchers include includeMatchers expectIDs + expectedError func(err error) bool }{ - // no filters, return all { + "no filters, return all", excludeMatchers{}, includeMatchers{}, expectIDs{ @@ -29,9 +32,10 @@ func TestFilter(t *testing.T) { "extensions/v1beta1:deployment:app:app", "/v1:configmap:app:app", }, + noError, }, - // exclude service accounts { + "exclude service accounts", excludeMatchers{ { Kind: "ServiceAccount", @@ -44,9 +48,10 @@ func TestFilter(t *testing.T) { "extensions/v1beta1:deployment:app:app", "/v1:configmap:app:app", }, + noError, }, - // exclude service accounts and pods { + "exclude service accounts and pods", excludeMatchers{ { Kind: "ServiceAccount", @@ -61,9 +66,10 @@ func TestFilter(t *testing.T) { "extensions/v1beta1:deployment:app:app", "/v1:configmap:app:app", }, + noError, }, - // exclude deployments named "app" { + "exclude deployments named \"app\"", excludeMatchers{ { Kind: "deployment", @@ -78,9 +84,10 @@ func TestFilter(t *testing.T) { "extensions/v1beta1:deployment:test-ns:test-deployment", "/v1:configmap:app:app", }, + noError, }, - // include service accounts { + "include service accounts", excludeMatchers{}, includeMatchers{ { @@ -91,9 +98,10 @@ func TestFilter(t *testing.T) { "/v1:serviceaccount::test-sa", "/v1:serviceaccount::test-sa-2", }, + noError, }, - // include service accounts and pods { + "include service accounts and pods", excludeMatchers{}, includeMatchers{ { @@ -108,9 +116,10 @@ func TestFilter(t *testing.T) { "/v1:serviceaccount::test-sa-2", "/v1:pod:test-ns:test-pod", }, + noError, }, - // include service accounts and pods, but drop test-sa-2 { + "include service accounts and pods, but drop test-sa-2", excludeMatchers{ { Name: "test-sa-2", @@ -128,9 +137,10 @@ func TestFilter(t *testing.T) { "/v1:serviceaccount::test-sa", "/v1:pod:test-ns:test-pod", }, + noError, }, - // don't include duplicate resources { + "don't include duplicate resources", excludeMatchers{}, includeMatchers{ { @@ -144,22 +154,152 @@ func TestFilter(t *testing.T) { "/v1:serviceaccount::test-sa", "/v1:serviceaccount::test-sa-2", }, + noError, + }, + { + "label key selector", + excludeMatchers{}, + includeMatchers{ + { + LabelSelector: "app", + }, + }, + expectIDs{ + "/v1:serviceaccount::test-sa", + "/v1:serviceaccount::test-sa-2", + "/v1:pod:test-ns:test-pod", + "extensions/v1beta1:deployment:test-ns:test-deployment", + }, + noError, + }, + { + "label key/value selector", + excludeMatchers{}, + includeMatchers{ + { + LabelSelector: "app=test", + }, + }, + expectIDs{ + "/v1:serviceaccount::test-sa", + "/v1:pod:test-ns:test-pod", + "extensions/v1beta1:deployment:test-ns:test-deployment", + }, + noError, + }, + { + "label key/value selector", + excludeMatchers{}, + includeMatchers{ + { + LabelSelector: "app=test2", + }, + }, + expectIDs{ + "/v1:serviceaccount::test-sa-2", + }, + noError, + }, + { + "label != selector", + excludeMatchers{}, + includeMatchers{ + { + LabelSelector: "app!=test", + }, + }, + expectIDs{ + "/v1:serviceaccount::test-sa-2", + "extensions/v1beta1:deployment:app:app", + "/v1:configmap:app:app", + }, + noError, + }, + { + "exclude by label selector", + excludeMatchers{ + { + LabelSelector: "app==test", + }, + }, + includeMatchers{}, + expectIDs{ + "/v1:serviceaccount::test-sa-2", + "extensions/v1beta1:deployment:app:app", + "/v1:configmap:app:app", + }, + noError, + }, + { + "exclude by label != selector", + excludeMatchers{ + { + LabelSelector: "app!=test", + }, + }, + includeMatchers{}, + expectIDs{ + "/v1:serviceaccount::test-sa", + "/v1:pod:test-ns:test-pod", + "extensions/v1beta1:deployment:test-ns:test-deployment", + }, + noError, + }, + { + "bad matcher filter include error", + excludeMatchers{}, + includeMatchers{ + { + LabelSelector: "app===test", + }, + }, + expectIDs{ + "/v1:serviceaccount::test-sa", + "/v1:serviceaccount::test-sa-2", + "/v1:pod:test-ns:test-pod", + "extensions/v1beta1:deployment:test-ns:test-deployment", + "extensions/v1beta1:deployment:app:app", + "/v1:configmap:app:app", + }, + filter.IsMatcherParseError, + }, + { + "bad matcher filter exclude error", + excludeMatchers{ + { + LabelSelector: "app===test", + }, + }, + includeMatchers{}, + expectIDs{}, + filter.IsMatcherParseError, }, } for _, test := range tests { - f := &filter.Filter{test.include, test.exclude} + f := filter.New() + for _, m := range test.include { + f.AddInclude(m) + } + for _, m := range test.exclude { + f.AddExclude(m) + } + + results, err := f.Filter(input) + if !test.expectedError(err) { + t.Errorf("unexpected error for %s: %v", test.name, err) + t.FailNow() + } - results := f.Filter(input) if len(results) != len(test.expectIDs) { - t.Errorf("expected %d results, got %d\nincludes: %+v, excludes: %+v\nresults: %v", len(test.expectIDs), len(results), f.Include, f.Exclude, resourceIDs(results)) + t.Errorf("%s: expected %d results, got %d\nincludes: %+v, excludes: %+v\nresults: %v", test.name, len(test.expectIDs), len(results), f.Include, f.Exclude, resourceIDs(results)) t.FailNow() } for i, res := range results { id := res.ID() if id != test.expectIDs[i] { - t.Errorf("expected %s, got %s\nincludes: %v, excludes: %v", test.expectIDs[i], id, f.Include, f.Exclude) + t.Errorf("%s: expected %s, got %s\nincludes: %v, excludes: %v", test.name, test.expectIDs[i], id, f.Include, f.Exclude) t.FailNow() } } diff --git a/pkg/filter/matcher.go b/pkg/filter/matcher.go index f2d3d70..0c8389c 100644 --- a/pkg/filter/matcher.go +++ b/pkg/filter/matcher.go @@ -4,57 +4,91 @@ import ( "strings" "github.com/ryane/kfilt/pkg/resource" + "k8s.io/apimachinery/pkg/labels" ) +// Matcher represents match criteria type Matcher struct { - Group string - Version string - Kind string - Name string - Namespace string + Group string + Version string + Kind string + Name string + Namespace string + LabelSelector string } -func (s *Matcher) Match(r resource.Resource) bool { +// Match returns true if a Resource matches the criteria +func (s *Matcher) Match(r resource.Resource) (bool, error) { gvk := r.GroupVersionKind() if s.Group != "" && !strings.EqualFold(s.Group, gvk.Group) { - return false + return false, nil } if s.Version != "" && !strings.EqualFold(s.Version, gvk.Version) { - return false + return false, nil } if s.Kind != "" && !strings.EqualFold(s.Kind, gvk.Kind) { - return false + return false, nil } if s.Name != "" && !strings.EqualFold(s.Name, r.GetName()) { - return false + return false, nil } if s.Namespace != "" { ns := r.GetNamespace() if strings.ToLower(s.Namespace) == "default" { if ns != "" && !strings.EqualFold(s.Namespace, ns) { - return false + return false, nil } } else if !strings.EqualFold(s.Namespace, ns) { - return false + return false, nil } } - return true + if s.LabelSelector != "" { + selector, err := labels.Parse(s.LabelSelector) + if err != nil { + return false, newMatcherParseError("invalid label selector: %v", err) + } + labelSet := labels.Set{} + for name, val := range r.GetLabels() { + labelSet[name] = val + } + + if !selector.Matches(labelSet) { + return false, nil + } + } + + return true, nil } +// NewMatcher creates a Matcher func NewMatcher(q string) (Matcher, error) { m := Matcher{} - criteria := strings.Split(q, ",") + + var criteria []string + parts := strings.Split(q, ",") + for _, part := range parts { + if part != "" { + criteria = append(criteria, part) + } + } if len(criteria) == 0 { - return m, newMatcherParseError("invalid matcher %q. query is required", q) + return m, newMatcherParseError( + "invalid matcher %q. query is required", + q, + ) } for _, criterion := range criteria { - parts := strings.Split(criterion, "=") + parts := strings.SplitN(criterion, "=", 2) if len(parts) != 2 { - return m, newMatcherParseError("invalid matcher %q. Should be in the format %q", criterion, "key=value") + return m, newMatcherParseError( + "invalid matcher %q. Should be in the format %q", + criterion, + "key=value", + ) } key, val := strings.ToLower(parts[0]), parts[1] @@ -70,8 +104,14 @@ func NewMatcher(q string) (Matcher, error) { m.Version = val case "namespace", "ns": m.Namespace = val + case "labels", "l": + m.LabelSelector = val default: - return m, newMatcherParseError("invalid matcher %q. key should be one of %v", criterion, validMatcherKeys()) + return m, newMatcherParseError( + "invalid matcher %q. key should be one of %v", + criterion, + validMatcherKeys(), + ) } } @@ -85,5 +125,6 @@ func validMatcherKeys() []string { "group", "g", "version", "v", "namespace", "ns", + "labels", "l", } } diff --git a/pkg/filter/matcher_test.go b/pkg/filter/matcher_test.go index 1621ac6..4e1ca05 100644 --- a/pkg/filter/matcher_test.go +++ b/pkg/filter/matcher_test.go @@ -24,9 +24,9 @@ func TestNewMatcher(t *testing.T) { { "k=secret", filter.Matcher{Kind: "secret"}, - func(error) bool { return true }, + noError, }, - // last kind winws + // last kind wins { "kind=Secret,kind=ServiceAccount", filter.Matcher{Kind: "ServiceAccount"}, @@ -44,6 +44,24 @@ func TestNewMatcher(t *testing.T) { filter.Matcher{Name: "test"}, noError, }, + // namespace + { + "namespace=test-ns", + filter.Matcher{Namespace: "test-ns"}, + noError, + }, + // labels + { + "labels=app=test", + filter.Matcher{LabelSelector: "app=test"}, + noError, + }, + // labels != + { + "l=app!=test", + filter.Matcher{LabelSelector: "app!=test"}, + noError, + }, // no matcher { "", @@ -74,13 +92,15 @@ func TestNewMatcher(t *testing.T) { } func TestMatcher(t *testing.T) { + var noError = func(err error) bool { return err == nil } tests := []struct { - matcher filter.Matcher - resource resource.Resource - expected bool + matcher filter.Matcher + resource resource.Resource + expected bool + expectedError func(err error) bool }{ // empty matcher should match - {filter.Matcher{}, role(), true}, + {filter.Matcher{}, role(), true, noError}, // kind matchers { filter.Matcher{ @@ -88,6 +108,7 @@ func TestMatcher(t *testing.T) { }, role(), true, + noError, }, { filter.Matcher{ @@ -95,6 +116,7 @@ func TestMatcher(t *testing.T) { }, role(), true, + noError, }, { filter.Matcher{ @@ -102,6 +124,7 @@ func TestMatcher(t *testing.T) { }, role(), false, + noError, }, { filter.Matcher{ @@ -109,6 +132,7 @@ func TestMatcher(t *testing.T) { }, serviceAccount(), true, + noError, }, { filter.Matcher{ @@ -116,6 +140,15 @@ func TestMatcher(t *testing.T) { }, serviceAccount(), true, + noError, + }, + { + filter.Matcher{ + Group: "", Version: "v1", Kind: "ServiceAccount", + }, + role(), + false, + noError, }, { filter.Matcher{ @@ -123,6 +156,7 @@ func TestMatcher(t *testing.T) { }, role(), true, + noError, }, { filter.Matcher{ @@ -130,6 +164,7 @@ func TestMatcher(t *testing.T) { }, role(), false, + noError, }, { filter.Matcher{ @@ -140,6 +175,7 @@ func TestMatcher(t *testing.T) { }, role(), true, + noError, }, { filter.Matcher{ @@ -150,6 +186,7 @@ func TestMatcher(t *testing.T) { }, role(), false, + noError, }, { filter.Matcher{ @@ -161,6 +198,7 @@ func TestMatcher(t *testing.T) { }, role(), true, + noError, }, { filter.Matcher{ @@ -172,6 +210,7 @@ func TestMatcher(t *testing.T) { }, role(), true, + noError, }, { filter.Matcher{ @@ -182,6 +221,7 @@ func TestMatcher(t *testing.T) { }, serviceAccount(), true, + noError, }, { filter.Matcher{ @@ -191,6 +231,7 @@ func TestMatcher(t *testing.T) { }, serviceAccount(), true, + noError, }, { filter.Matcher{ @@ -201,12 +242,47 @@ func TestMatcher(t *testing.T) { }, serviceAccount(), false, + noError, + }, + { + filter.Matcher{ + Namespace: "test-ns", + }, + serviceAccount(), + false, + noError, + }, + { + filter.Matcher{LabelSelector: "app=test"}, + serviceAccount(), + true, + noError, + }, + { + filter.Matcher{LabelSelector: "app=test"}, + role(), + false, + noError, + }, + // bad label selector + { + filter.Matcher{LabelSelector: "app**test"}, + role(), + false, + filter.IsMatcherParseError, }, } for _, test := range tests { - if result := test.matcher.Match(test.resource); result != test.expected { - t.Errorf("expected %v for %v, got %v", test.expected, test.matcher, result) + result, err := test.matcher.Match(test.resource) + + if !test.expectedError(err) { + t.Errorf("unexpected error %v for %+v", err, test.matcher) + t.FailNow() + } + + if result != test.expected { + t.Errorf("expected %v for %+v, got %v", test.expected, test.matcher, result) t.FailNow() } } @@ -232,6 +308,9 @@ func serviceAccount() resource.Resource { "metadata": map[string]interface{}{ "name": "test-sa", "namespace": "monitoring", + "labels": map[string]interface{}{ + "app": "test", + }, }, }, ) diff --git a/pkg/filter/test_data_test.go b/pkg/filter/test_data_test.go index eb7f148..0ac1653 100644 --- a/pkg/filter/test_data_test.go +++ b/pkg/filter/test_data_test.go @@ -9,6 +9,9 @@ var input = []resource.Resource{ "kind": "ServiceAccount", "metadata": map[string]interface{}{ "name": "test-sa", + "labels": map[string]interface{}{ + "app": "test", + }, }, }, ), @@ -18,6 +21,9 @@ var input = []resource.Resource{ "kind": "ServiceAccount", "metadata": map[string]interface{}{ "name": "test-sa-2", + "labels": map[string]interface{}{ + "app": "test2", + }, }, }, ), @@ -28,6 +34,10 @@ var input = []resource.Resource{ "metadata": map[string]interface{}{ "name": "test-pod", "namespace": "test-ns", + "labels": map[string]interface{}{ + "app": "test", + "version": "v1", + }, }, }, ), @@ -38,6 +48,9 @@ var input = []resource.Resource{ "metadata": map[string]interface{}{ "name": "test-deployment", "namespace": "test-ns", + "labels": map[string]interface{}{ + "app": "test", + }, }, }, ), diff --git a/plugin/kustomize/Dockerfile b/plugin/kustomize/Dockerfile index c533f10..c5218c1 100644 --- a/plugin/kustomize/Dockerfile +++ b/plugin/kustomize/Dockerfile @@ -15,7 +15,7 @@ RUN mkdir -p sigs.k8s.io && \ COPY kfilt.go sigs.k8s.io/kustomize/plugin/ RUN cd sigs.k8s.io/kustomize && \ - go get github.com/ryane/kfilt@plugin && \ + go get github.com/ryane/kfilt && \ go build -buildmode plugin -o ~/.config/kustomize/plugin/ryane/v1beta1/kfilter/Kfilter.so plugin/kfilt.go ENTRYPOINT ["/bin/kustomize"] diff --git a/plugin/kustomize/kfilt.go b/plugin/kustomize/kfilt.go index c64aa58..54cb5ff 100644 --- a/plugin/kustomize/kfilt.go +++ b/plugin/kustomize/kfilt.go @@ -56,7 +56,11 @@ func (p *plugin) Transform(m resmap.ResMap) error { // create new resmap with filtered resources kresmap := resmap.New() kresourceFactory := p.rf.RF() - for _, res := range kfilt.Filter(resources) { + filtered, err := kfilt.Filter(resources) + if err != nil { + return err + } + for _, res := range filtered { kres := kresourceFactory.FromMap(res.Object) if err := kresmap.Append(kres); err != nil { return err