├── go.sum ├── .gitignore ├── go.mod ├── release ├── kube-s_v1.0.0_darwin_amd64 ├── kube-s_v1.0.0_windows_amd64.exe └── create-release.sh ├── benchmark ├── run.sh └── cmd.sh ├── main.go ├── pkg └── search │ ├── kubectlwrapper.go │ └── search.go ├── LICENSE └── README.md /go.sum: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/binura-g/kube-s 2 | 3 | go 1.13 4 | -------------------------------------------------------------------------------- /release/kube-s_v1.0.0_darwin_amd64: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binura-g/kube-s/HEAD/release/kube-s_v1.0.0_darwin_amd64 -------------------------------------------------------------------------------- /release/kube-s_v1.0.0_windows_amd64.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/binura-g/kube-s/HEAD/release/kube-s_v1.0.0_windows_amd64.exe -------------------------------------------------------------------------------- /benchmark/run.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Benchmarks" 4 | 5 | echo "Running bash script (iterating over each cluster and using grep)" 6 | time _=$(./cmd.sh "$1") 7 | 8 | echo -e "\n-----\nRunning kube-s\n" 9 | time _=$(go run ../main.go "$1") 10 | -------------------------------------------------------------------------------- /benchmark/cmd.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # First argument is used as the pattern 4 | PATTERN=$1 5 | 6 | IFS=$'\n'; CLUSTERS=($(kubectl config get-clusters)); unset IFS; 7 | 8 | # Remove first line (NAME) 9 | unset 'CLUSTERS[0]' 10 | 11 | for i in "${CLUSTERS[@]}" 12 | do 13 | _=$(kubectl config use-context "$i") 14 | kubectl get pods --all-namespaces | grep "$PATTERN" 15 | done 16 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "github.com/binura-g/kube-s/pkg/search" 5 | "log" 6 | "os" 7 | ) 8 | 9 | func main() { 10 | log.SetFlags(0) 11 | if len(os.Args) != 3 { 12 | log.Println("Usage:\n\tkube-s \n\tEg. kube-s pods my-app") 13 | os.Exit(1) 14 | } 15 | 16 | kind := os.Args[1] 17 | pattern := os.Args[2] 18 | resultsChannel := make(chan search.Result) 19 | go search.Run(kind, pattern, resultsChannel) 20 | 21 | for r := range resultsChannel { 22 | log.Printf("%s\t%s\t\t%s\n", r.Cluster, r.Ns, r.Name) 23 | } 24 | 25 | os.Exit(0) 26 | } 27 | -------------------------------------------------------------------------------- /release/create-release.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | VERSION=$1 4 | 5 | package="github.com/binura-g/kube-s" 6 | 7 | package_split=(${package//\// }) 8 | package_name=${package_split[-1]} 9 | 10 | platforms=("windows/amd64" "darwin/amd64") 11 | for platform in "${platforms[@]}" 12 | do 13 | platform_split=(${platform//\// }) 14 | GOOS=${platform_split[0]} 15 | GOARCH=${platform_split[1]} 16 | output_name=$package_name'_'$VERSION'_'$GOOS'_'$GOARCH 17 | if [ $GOOS = "windows" ]; then 18 | output_name+='.exe' 19 | fi 20 | 21 | env GOOS=$GOOS GOARCH=$GOARCH go build -o $output_name $package 22 | if [ $? -ne 0 ]; then 23 | echo 'An error has occurred. Aborting.' 24 | exit 1 25 | fi 26 | done -------------------------------------------------------------------------------- /pkg/search/kubectlwrapper.go: -------------------------------------------------------------------------------- 1 | package search 2 | 3 | import ( 4 | "fmt" 5 | "os/exec" 6 | "strings" 7 | ) 8 | 9 | func listClusters() ([]string, error) { 10 | output, err := exec.Command("kubectl", "config", "get-clusters").CombinedOutput() 11 | if err != nil { 12 | return nil, fmt.Errorf("%s %v", output, err) 13 | } 14 | 15 | clusters := strings.Split(strings.Trim(string(output), "\n"), "\n")[1:] 16 | return clusters, nil 17 | } 18 | 19 | func listResources(cluster, kind string) ([]byte, error) { 20 | args := []string{ 21 | "get", kind, 22 | "--all-namespaces", 23 | "--no-headers", 24 | "-o=custom-columns=NAMESPACE:.metadata.namespace,NAME:.metadata.name", 25 | fmt.Sprintf("--context=%s", cluster), 26 | } 27 | return exec.Command("kubectl", args...).CombinedOutput() 28 | } 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2019 Binura Gunasekara 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | KUBE-S 2 | --- 3 | 4 | [![license](https://img.shields.io/github/license/DAVFoundation/captain-n3m0.svg?style=flat-square)](https://github.com/binura-g/kube-s/blob/master/LICENSE) 5 | [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg?style=flat-square)](http://makeapullrequest.com) 6 | 7 | A lightweight CLI tool for quickly finding specific k8s resources (by pattern matching Names) across all clusters available to kubectl. 8 | 9 | ### Why not use a bash script? kube-s is **FAST**! 10 | 11 | **kube-s searches through all your clusters concurrently and is much faster than searching through each cluster with something like `grep`.** 12 | 13 | In general, `kube-s` outperforms an equivalent bash script search by a few good seconds. The higher the number of clusters, the more significant this difference becomes. 14 | 15 | > You can find the scripts to run benchmarks under `./benchmark`. 16 | 17 | ## Usage 18 | 19 | `$ kube-s ` 20 | 21 | *Eg.* Search for all pods with names matching `"my-"` 22 | 23 | `$ kube-s pods my-` 24 | 25 | Result: 26 | ``` 27 | cluster-01 namespace-01 my-app-1 28 | cluster-02 namespace-01 my-app-2 29 | cluster-03 namespace-02 my-app-1 30 | ``` 31 | 32 | > kube-s searches all clusters available in your kubeconfig 33 | 34 | ## Installation 35 | 36 | Install globally using go-get (Requires Go 1.13+) 37 | 38 | `go get github.com/binura-g/kube-s` 39 | 40 | or Install from Release Build 41 | - Download the release specific to your OS from `./release` 42 | - Add the executable to your $PATH 43 | 44 | -------------------------------------------------------------------------------- /pkg/search/search.go: -------------------------------------------------------------------------------- 1 | package search 2 | 3 | import ( 4 | "bufio" 5 | "bytes" 6 | "log" 7 | "strings" 8 | "sync" 9 | ) 10 | 11 | type Result struct { 12 | Cluster string 13 | Ns string 14 | Name string 15 | } 16 | 17 | type kubectlOutput struct { 18 | Data []byte 19 | Cluster string 20 | } 21 | 22 | func Run(kind, pattern string, resultCh chan Result) { 23 | clusters, err := listClusters() 24 | if err != nil { 25 | log.Fatal(err) 26 | } 27 | 28 | kubectlOutputCh := make(chan kubectlOutput, len(clusters)) 29 | go func() { 30 | var wg sync.WaitGroup 31 | wg.Add(len(clusters)) 32 | 33 | for _, cluster := range clusters { 34 | go func(cluster string) { 35 | defer wg.Done() 36 | output, err := listResources(cluster, kind) 37 | if err != nil { 38 | log.Printf("Error: %q %s\n", cluster, output) 39 | } 40 | kubectlOutputCh <- kubectlOutput{output, cluster} 41 | }(cluster) 42 | } 43 | wg.Wait() 44 | close(kubectlOutputCh) 45 | }() 46 | 47 | var resultWg sync.WaitGroup 48 | for output := range kubectlOutputCh { 49 | resultWg.Add(1) 50 | 51 | go func(output kubectlOutput) { 52 | defer resultWg.Done() 53 | 54 | scanner := bufio.NewScanner(bytes.NewReader(output.Data)) 55 | for scanner.Scan() { 56 | s := scanner.Text() 57 | if strings.Contains(s, pattern) { 58 | split := strings.Split(s, " ") 59 | resultCh <- Result{ 60 | Cluster: output.Cluster, 61 | Ns: split[0], 62 | Name: split[len(split)-1], 63 | } 64 | } 65 | } 66 | }(output) 67 | } 68 | 69 | resultWg.Wait() 70 | close(resultCh) 71 | } 72 | --------------------------------------------------------------------------------