From 6ec39a45c8a364a89c12bd6f07d99257240d6745 Mon Sep 17 00:00:00 2001 From: ryane Date: Sat, 22 Jun 2019 21:51:16 -0400 Subject: [PATCH] initial commit (ported from kfind) --- .dockerignore | 1 + .gitignore | 1 + Dockerfile | 17 +++++++ Makefile | 20 ++++++++ README.md | 3 ++ VERSION | 1 + cmd/root.go | 89 ++++++++++++++++++++++++++++++++ cmd/version.go | 24 +++++++++ go.mod | 11 ++++ go.sum | 92 ++++++++++++++++++++++++++++++++++ main.go | 11 ++++ pkg/decoder/decoder.go | 41 +++++++++++++++ pkg/decoder/decoder_test.go | 51 +++++++++++++++++++ pkg/decoder/test.yaml | 31 ++++++++++++ pkg/filter/filter.go | 41 +++++++++++++++ pkg/filter/filter_test.go | 42 ++++++++++++++++ pkg/filter/kind_filter.go | 41 +++++++++++++++ pkg/filter/kind_filter_test.go | 59 ++++++++++++++++++++++ pkg/filter/name_filter.go | 41 +++++++++++++++ pkg/filter/name_filter_test.go | 59 ++++++++++++++++++++++ pkg/filter/test_data_test.go | 40 +++++++++++++++ pkg/printer/printer.go | 39 ++++++++++++++ 22 files changed, 755 insertions(+) create mode 100644 .dockerignore create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 Makefile create mode 100644 README.md create mode 100644 VERSION create mode 100644 cmd/root.go create mode 100644 cmd/version.go create mode 100644 go.mod create mode 100644 go.sum create mode 100644 main.go create mode 100644 pkg/decoder/decoder.go create mode 100644 pkg/decoder/decoder_test.go create mode 100644 pkg/decoder/test.yaml create mode 100644 pkg/filter/filter.go create mode 100644 pkg/filter/filter_test.go create mode 100644 pkg/filter/kind_filter.go create mode 100644 pkg/filter/kind_filter_test.go create mode 100644 pkg/filter/name_filter.go create mode 100644 pkg/filter/name_filter_test.go create mode 100644 pkg/filter/test_data_test.go create mode 100644 pkg/printer/printer.go diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..33ceb8f --- /dev/null +++ b/.dockerignore @@ -0,0 +1 @@ +Makefile \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..8e21434 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +/kfilt diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..dd37a22 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM golang:1.12-alpine3.9 as builder + +RUN apk --no-cache add make git gcc musl-dev && rm -rf /var/cache/apk/* +WORKDIR /go/src/github.com/ryane/kfilt +RUN GO111MODULE=off go get github.com/ahmetb/govvv +ENV GO111MODULE=on +COPY go.mod . +COPY go.sum . +RUN go mod download + +COPY . . +RUN govvv install -pkg github.com/ryane/kfilt/cmd + +FROM alpine:3.9 +RUN apk --no-cache add ca-certificates && rm -rf /var/cache/apk/* +COPY --from=builder /go/bin/kfilt /bin/kfilt +ENTRYPOINT ["/bin/kfilt"] diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..6e854ad --- /dev/null +++ b/Makefile @@ -0,0 +1,20 @@ +VERSION ?= $(shell cat VERSION) +GIT_DIRTY = $(shell test -n "`git status --porcelain`" && echo "-dirty") + +.PHONY: all +all: build + +.PHONY: build +build: deps + govvv build -pkg github.com/ryane/kfilt/cmd + +.PHONY: docker +docker: + docker build -t ryane/kfilt:${VERSION}${GIT_DIRTY} . + +push: build + docker push ryane/kfilt:${VERSION}${GIT_DIRTY} + +.PHONY: deps +deps: + GO111MODULE=off go get github.com/ahmetb/govvv diff --git a/README.md b/README.md new file mode 100644 index 0000000..d685bd6 --- /dev/null +++ b/README.md @@ -0,0 +1,3 @@ +# kfilt + +kfilt can filter Kubernetes resources. diff --git a/VERSION b/VERSION new file mode 100644 index 0000000..4e379d2 --- /dev/null +++ b/VERSION @@ -0,0 +1 @@ +0.0.2 diff --git a/cmd/root.go b/cmd/root.go new file mode 100644 index 0000000..a297e22 --- /dev/null +++ b/cmd/root.go @@ -0,0 +1,89 @@ +package cmd + +import ( + "fmt" + "io/ioutil" + "os" + + "github.com/pkg/errors" + "github.com/ryane/kfilt/pkg/decoder" + "github.com/ryane/kfilt/pkg/filter" + "github.com/ryane/kfilt/pkg/printer" + log "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +type root struct { + kind string + name string + filename string +} + +func newRootCommand(args []string) *cobra.Command { + root := &root{} + rootCmd := &cobra.Command{ + Use: "kfilt", + Short: "kfilt can filter Kubernetes resources", + Long: `kfilt can filter Kubernetes resources`, + Run: func(cmd *cobra.Command, args []string) { + if err := root.run(); err != nil { + log.WithError(err).Error() + os.Exit(1) + } + }, + } + + rootCmd.Flags().StringVarP(&root.kind, "kind", "k", "", "Only include resources of kind") + rootCmd.Flags().StringVarP(&root.name, "name", "n", "", "Only include resources of name") + rootCmd.Flags().StringVarP(&root.filename, "filename", "f", "", "Read manifests from file") + + rootCmd.AddCommand(newVersionCommand()) + + return rootCmd +} + +func (r *root) run() error { + var ( + in []byte + err error + ) + + // get input + if r.filename != "" { + in, err = ioutil.ReadFile(r.filename) + if err != nil { + return errors.Wrap(err, fmt.Sprintf("failed to read file %q", r.filename)) + } + } else { + in, err = ioutil.ReadAll(os.Stdin) + if err != nil { + return errors.Wrap(err, "failed to read stdin") + } + } + + // decode + results, err := decoder.New().Decode(in) + if err != nil { + return err + } + + // filter + filtered := filter.New( + filter.KindMatcher([]string{r.kind}), + filter.NameMatcher([]string{r.name}), + ).Filter(results) + + // print + if err := printer.New().Print(filtered); err != nil { + return err + } + + return nil +} + +func Execute(args []string) { + if err := newRootCommand(args).Execute(); err != nil { + log.WithError(err).Error() + os.Exit(2) + } +} diff --git a/cmd/version.go b/cmd/version.go new file mode 100644 index 0000000..2fc7b18 --- /dev/null +++ b/cmd/version.go @@ -0,0 +1,24 @@ +package cmd + +import ( + "fmt" + + "github.com/spf13/cobra" +) + +var GitCommit, GitState, Version string + +func newVersionCommand() *cobra.Command { + return &cobra.Command{ + Use: "version", + Short: "Display the kfilt version", + Run: func(cmd *cobra.Command, args []string) { + + if GitState != "clean" { + fmt.Printf("%s (%s-%s)\n", Version, GitCommit, GitState) + } else { + fmt.Printf("%s (%s)\n", Version, GitCommit) + } + }, + } +} diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2988130 --- /dev/null +++ b/go.mod @@ -0,0 +1,11 @@ +module github.com/ryane/kfilt + +go 1.12 + +require ( + github.com/pkg/errors v0.8.1 + github.com/sirupsen/logrus v1.4.1 + github.com/spf13/cobra v0.0.5 + gopkg.in/yaml.v2 v2.2.2 + k8s.io/apimachinery v0.0.0-20190612125636-6a5db36e93ad +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2552526 --- /dev/null +++ b/go.sum @@ -0,0 +1,92 @@ +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/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/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/golang/groupcache v0.0.0-20160516000752-02826c3e7903/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +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/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/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +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/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +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/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 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/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/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 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/sirupsen/logrus v1.4.1 h1:GL2rEmy6nsikmW0r8opw9JIRScdMF5hA8cOYLH7In1k= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +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.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 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/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +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= +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/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/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/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-20190312061237-fead79001313 h1:pczuHS43Cp2ktBEEmLwScxgjWsBSzdaQiKzUyf3DTTc= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +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/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +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/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/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/kube-openapi v0.0.0-20190228160746-b3a7cee44a30/go.mod h1:BXM9ceUBTj2QnfH2MK1odQs778ajze1RxcmP6S8RVVc= +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/main.go b/main.go new file mode 100644 index 0000000..657afb8 --- /dev/null +++ b/main.go @@ -0,0 +1,11 @@ +package main + +import ( + "os" + + "github.com/ryane/kfilt/cmd" +) + +func main() { + cmd.Execute(os.Args[1:]) +} diff --git a/pkg/decoder/decoder.go b/pkg/decoder/decoder.go new file mode 100644 index 0000000..f1d2bcf --- /dev/null +++ b/pkg/decoder/decoder.go @@ -0,0 +1,41 @@ +package decoder + +import ( + "bytes" + "github.com/pkg/errors" + "io" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" + k8syaml "k8s.io/apimachinery/pkg/util/yaml" +) + +type Decoder interface { + Decode([]byte) ([]unstructured.Unstructured, error) +} + +type kubernetesDecoder struct{} + +func New() Decoder { + return &kubernetesDecoder{} +} + +func (k *kubernetesDecoder) Decode(in []byte) ([]unstructured.Unstructured, error) { + var ( + result []unstructured.Unstructured + err error + ) + + decoder := k8syaml.NewYAMLOrJSONDecoder(bytes.NewReader(in), 1024) + + for err == nil { + var out unstructured.Unstructured + err = decoder.Decode(&out) + if err == nil { + result = append(result, out) + } + } + if err != io.EOF { + return nil, errors.Wrap(err, "failed to decode input") + } + + return result, nil +} diff --git a/pkg/decoder/decoder_test.go b/pkg/decoder/decoder_test.go new file mode 100644 index 0000000..aadef82 --- /dev/null +++ b/pkg/decoder/decoder_test.go @@ -0,0 +1,51 @@ +package decoder_test + +import ( + "io/ioutil" + "testing" + + "github.com/ryane/kfilt/pkg/decoder" +) + +func TestDecoder(t *testing.T) { + // load test data + file := "./test.yaml" + in, err := ioutil.ReadFile(file) + if err != nil { + t.Errorf("error loading test data: %v", err) + t.FailNow() + } + + // decode + d := decoder.New() + results, err := d.Decode(in) + + if err != nil { + t.Errorf("unexpected error: %v", err) + t.FailNow() + } + + expectedCount := 5 + if len(results) != expectedCount { + t.Errorf("expected %d results, got %d", expectedCount, len(results)) + t.FailNow() + } + + expectNames := []string{"test", "test2", "example-config", "cluster-specification", "handler"} + for i, u := range results { + name := u.GetName() + if name != expectNames[i] { + t.Errorf("expected %s, got %s", expectNames[i], name) + t.FailNow() + } + } + + expectKinds := []string{"ServiceAccount", "ServiceAccount", "ConfigMap", "ClusterSpec", "stdio"} + for i, u := range results { + kind := u.GetKind() + if kind != expectKinds[i] { + t.Errorf("expected %s, got %s", expectKinds[i], kind) + t.FailNow() + } + } +} diff --git a/pkg/decoder/test.yaml b/pkg/decoder/test.yaml new file mode 100644 index 0000000..a54bcf7 --- /dev/null +++ b/pkg/decoder/test.yaml @@ -0,0 +1,31 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: test +--- +apiVersion: v1 +kind: ServiceAccount +metadata: + name: test2 +--- +apiVersion: v1 +kind: ConfigMap +metadata: + name: example-config +data: + key: val +--- +apiVersion: kubernetes.example.com/v1beta1 +kind: ClusterSpec +metadata: + name: cluster-specification +spec: + clusterName: kube +--- +apiVersion: "config.istio.io/v1alpha2" +kind: stdio +metadata: + name: handler + namespace: istio-system +spec: + outputAsJson: true diff --git a/pkg/filter/filter.go b/pkg/filter/filter.go new file mode 100644 index 0000000..798c76f --- /dev/null +++ b/pkg/filter/filter.go @@ -0,0 +1,41 @@ +package filter + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type Filter interface { + Filter([]unstructured.Unstructured) []unstructured.Unstructured +} + +type defaultFilter struct { + matchers []Matcher +} + +type Matcher interface { + Match(unstructured.Unstructured) bool +} + +func New(matchers ...Matcher) Filter { + return &defaultFilter{matchers} +} + +func (f *defaultFilter) Filter(unstructureds []unstructured.Unstructured) []unstructured.Unstructured { + filtered := unstructureds + + for _, matcher := range f.matchers { + filtered = filter(filtered, matcher) + } + + return filtered +} + +func filter(unstructureds []unstructured.Unstructured, matcher Matcher) []unstructured.Unstructured { + filtered := []unstructured.Unstructured{} + for _, u := range unstructureds { + if matcher.Match(u) { + filtered = append(filtered, u) + } + } + return filtered +} diff --git a/pkg/filter/filter_test.go b/pkg/filter/filter_test.go new file mode 100644 index 0000000..2c7a91d --- /dev/null +++ b/pkg/filter/filter_test.go @@ -0,0 +1,42 @@ +package filter_test + +import ( + "testing" + + "github.com/ryane/kfilt/pkg/filter" +) + +func TestFilter(t *testing.T) { + tests := []struct { + names []string + kinds []string + expectNames []string + }{ + {[]string{"test-sa"}, []string{"Deployment"}, []string{}}, + {[]string{"test-sa"}, []string{}, []string{"test-sa"}}, + {[]string{"test-sa"}, []string{""}, []string{"test-sa"}}, + {[]string{}, []string{"ServiceAccount"}, []string{"test-sa", "test-sa-2"}}, + {[]string{"test-pod", "test-deployment"}, []string{"ServiceAccount"}, []string{}}, + } + + for _, test := range tests { + f := filter.New( + filter.KindMatcher(test.kinds), + filter.NameMatcher(test.names), + ) + + results := f.Filter(input) + if len(results) != len(test.expectNames) { + t.Errorf("expected %d results, got %d", len(test.expectNames), len(results)) + t.FailNow() + } + + for i, u := range results { + name := u.GetName() + if name != test.expectNames[i] { + t.Errorf("expected %s, got %s", test.expectNames[i], name) + t.FailNow() + } + } + } +} diff --git a/pkg/filter/kind_filter.go b/pkg/filter/kind_filter.go new file mode 100644 index 0000000..aa69115 --- /dev/null +++ b/pkg/filter/kind_filter.go @@ -0,0 +1,41 @@ +package filter + +import ( + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type kindMatcher struct { + kinds []string +} + +func KindMatcher(kinds []string) Matcher { + return &kindMatcher{validKinds(kinds)} +} + +func (f *kindMatcher) Match(u unstructured.Unstructured) bool { + // no kinds specified so we just return a match + if len(f.kinds) == 0 { + return true + } + + for _, kind := range f.kinds { + if kind == u.GetKind() { + return true + } + } + + return false +} + +func validKinds(kinds []string) []string { + valid := []string{} + for _, kind := range kinds { + trimKind := strings.TrimSpace(kind) + if len(trimKind) > 0 { + valid = append(valid, trimKind) + } + } + return valid +} diff --git a/pkg/filter/kind_filter_test.go b/pkg/filter/kind_filter_test.go new file mode 100644 index 0000000..f89bcc4 --- /dev/null +++ b/pkg/filter/kind_filter_test.go @@ -0,0 +1,59 @@ +package filter_test + +import ( + "testing" + + "github.com/ryane/kfilt/pkg/filter" +) + +func TestKindFilterNil(t *testing.T) { + matcher := filter.KindMatcher(nil) + + for _, u := range input { + if !matcher.Match(u) { + t.Errorf("expected match for %s", u.GetKind()) + t.FailNow() + } + } +} + +func TestKindFilterEmptyVals(t *testing.T) { + matcher := filter.KindMatcher([]string{" ", ""}) + + for _, u := range input { + if !matcher.Match(u) { + t.Errorf("expected match for %s", u.GetKind()) + t.FailNow() + } + } +} + +func TestKindFilter(t *testing.T) { + tests := []struct { + kinds []string + expectNames []string + }{ + {[]string{"ServiceAccount"}, []string{"test-sa", "test-sa-2"}}, + {[]string{"Deployment"}, []string{"test-deployment"}}, + {[]string{"Pod"}, []string{"test-pod"}}, + {[]string{"ServiceAccount", "Deployment"}, []string{"test-sa", "test-sa-2", "test-deployment"}}, + } + + for _, test := range tests { + f := filter.New(filter.KindMatcher(test.kinds)) + + results := f.Filter(input) + if len(results) != len(test.expectNames) { + t.Errorf("expected %d results, got %d", len(test.expectNames), len(results)) + t.FailNow() + } + + for i, u := range results { + name := u.GetName() + if name != test.expectNames[i] { + t.Errorf("expected %s, got %s", test.expectNames[i], name) + t.FailNow() + } + } + } +} diff --git a/pkg/filter/name_filter.go b/pkg/filter/name_filter.go new file mode 100644 index 0000000..f933f23 --- /dev/null +++ b/pkg/filter/name_filter.go @@ -0,0 +1,41 @@ +package filter + +import ( + "strings" + + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type nameMatcher struct { + names []string +} + +func NameMatcher(names []string) Matcher { + return &nameMatcher{validNames(names)} +} + +func (f *nameMatcher) Match(u unstructured.Unstructured) bool { + // no names specified so we just return a match + if len(f.names) == 0 { + return true + } + + for _, name := range f.names { + if name == u.GetName() { + return true + } + } + + return false +} + +func validNames(names []string) []string { + valid := []string{} + for _, name := range names { + trimName := strings.TrimSpace(name) + if len(trimName) > 0 { + valid = append(valid, trimName) + } + } + return valid +} diff --git a/pkg/filter/name_filter_test.go b/pkg/filter/name_filter_test.go new file mode 100644 index 0000000..e63eefe --- /dev/null +++ b/pkg/filter/name_filter_test.go @@ -0,0 +1,59 @@ +package filter_test + +import ( + "testing" + + "github.com/ryane/kfilt/pkg/filter" +) + +func TestNameFilterNil(t *testing.T) { + matcher := filter.NameMatcher(nil) + + for _, u := range input { + if !matcher.Match(u) { + t.Errorf("expected match for %s", u.GetName()) + t.FailNow() + } + } +} + +func TestNameFilterEmptyVals(t *testing.T) { + matcher := filter.NameMatcher([]string{" ", ""}) + + for _, u := range input { + if !matcher.Match(u) { + t.Errorf("expected match for %s", u.GetName()) + t.FailNow() + } + } +} + +func TestNameFilter(t *testing.T) { + tests := []struct { + names []string + expectNames []string + }{ + {[]string{"test-sa"}, []string{"test-sa"}}, + {[]string{"test-sa", "test-sa-2"}, []string{"test-sa", "test-sa-2"}}, + {[]string{"test-deployment"}, []string{"test-deployment"}}, + {[]string{"test-deployment", "test-pod"}, []string{"test-pod", "test-deployment"}}, + } + + for _, test := range tests { + f := filter.New(filter.NameMatcher(test.names)) + + results := f.Filter(input) + if len(results) != len(test.expectNames) { + t.Errorf("expected %d results, got %d", len(test.expectNames), len(results)) + t.FailNow() + } + + for i, u := range results { + name := u.GetName() + if name != test.expectNames[i] { + t.Errorf("expected %s, got %s", test.expectNames[i], name) + t.FailNow() + } + } + } +} diff --git a/pkg/filter/test_data_test.go b/pkg/filter/test_data_test.go new file mode 100644 index 0000000..7af15db --- /dev/null +++ b/pkg/filter/test_data_test.go @@ -0,0 +1,40 @@ +package filter_test + +import ( + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +var input = []unstructured.Unstructured{ + { + Object: map[string]interface{}{ + "kind": "ServiceAccount", + "metadata": map[string]interface{}{ + "name": "test-sa", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "ServiceAccount", + "metadata": map[string]interface{}{ + "name": "test-sa-2", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "Pod", + "metadata": map[string]interface{}{ + "name": "test-pod", + }, + }, + }, + { + Object: map[string]interface{}{ + "kind": "Deployment", + "metadata": map[string]interface{}{ + "name": "test-deployment", + }, + }, + }, +} diff --git a/pkg/printer/printer.go b/pkg/printer/printer.go new file mode 100644 index 0000000..423efc9 --- /dev/null +++ b/pkg/printer/printer.go @@ -0,0 +1,39 @@ +package printer + +import ( + "fmt" + "github.com/pkg/errors" + yaml "gopkg.in/yaml.v2" + "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" +) + +type Printer interface { + Print([]unstructured.Unstructured) error +} + +type consolePrinter struct{} + +func New() Printer { + return &consolePrinter{} +} + +func (p *consolePrinter) Print(unstructureds []unstructured.Unstructured) error { + for _, u := range unstructureds { + if err := p.printUnstructured(u); err != nil { + return err + } + } + return nil +} + +func (p *consolePrinter) printUnstructured(u unstructured.Unstructured) error { + data, err := yaml.Marshal(u.Object) + if err != nil { + return errors.Wrap(err, "failed to marshal yaml") + } + + fmt.Println("---") + fmt.Println(string(data)) + + return nil +}