├── imgs ├── screenshot_1.png └── screenshot_2.png ├── go.mod ├── main.go ├── README.md ├── LICENSE ├── utils.go ├── functions.go └── go.sum /imgs/screenshot_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claranet/kcs/HEAD/imgs/screenshot_1.png -------------------------------------------------------------------------------- /imgs/screenshot_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/claranet/kcs/HEAD/imgs/screenshot_2.png -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com:claranet/aps 2 | 3 | go 1.12 4 | 5 | require ( 6 | github.com/manifoldco/promptui v0.3.3-0.20190411181407-35bab80e16a4 7 | gopkg.in/yaml.v2 v2.2.2 8 | ) 9 | -------------------------------------------------------------------------------- /main.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import "os" 4 | 5 | func main() { 6 | kc := selectKubeconfig() 7 | startNewShell(kc) 8 | 9 | // Avoid stacked shell sessions, when exit/ctrl+D caller shell is killed 10 | process, _ := os.FindProcess(os.Getppid()) 11 | process.Kill() 12 | } 13 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # KCS 2 | 3 | ## Description 4 | 5 | Select which *kubeconfig.yaml* to use in an easy way. **KCS** means **kubeconfig switcher**. 6 | 7 | Features : 8 | - Select item with ←, ↑, → ↓ 9 | - Filter by a search 10 | - Display contexts of selected kube config file 11 | 12 | ![screenshot_1](./imgs/screenshot_1.png) 13 | ![screenshot_2](./imgs/screenshot_2.png) 14 | 15 | ## Configuration 16 | 17 | ### Create config file 18 | ``` 19 | echo "KUBECONFIG_FILES=XXX" > $HOME/.kcs 20 | ``` 21 | with `XXX` the full path to your directory with *kubeconfig.yaml* files 22 | 23 | ### Add binary 24 | ``` 25 | sudo cp kcs /usr/local/bin/ 26 | ``` 27 | 28 | ## Usage 29 | ``` 30 | kcs 31 | ``` 32 | ``` 33 | $ kcs 34 | Search: █ 35 | ? : 36 | kube-config.apps.prod.eu-west-1.XXX.yaml 37 | > kube-config.XXXXX.prod.eu-west-3.XXXXX.yaml 38 | kube-config.docker4windows.yml 39 | XXXXX_kube-config_prod.yaml 40 | 41 | Contexts: claranet-rw-XXXX.prod.eu-west-3.XXXXX 42 | ``` 43 | 44 | ## Author 45 | 46 | Thomas Labarussias (thomas.labarussias@fr.clara.net - https://github.com/Issif) 47 | 48 | 49 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Thomas Labarussias 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. -------------------------------------------------------------------------------- /utils.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "io/ioutil" 5 | "log" 6 | "os" 7 | "strings" 8 | 9 | yaml "gopkg.in/yaml.v2" 10 | ) 11 | 12 | func getConfig() string { 13 | home := os.Getenv("HOME") 14 | kcp, _ := ioutil.ReadFile(home + "/.kcs") 15 | kcs := strings.Split(string(kcp), "=") 16 | if kcs[0] == "KUBECONFIG_FILES" && len(kcs[1]) != 0 { 17 | return strings.TrimSuffix(kcs[1], "\n") 18 | } else { 19 | log.Fatal("wrong config") 20 | } 21 | return "" 22 | } 23 | 24 | type kubeconfigyaml struct { 25 | Contexts []context `yaml:"contexts"` 26 | } 27 | 28 | type context struct { 29 | Name string `yaml:"name"` 30 | } 31 | 32 | func getKubeconfigContexts(kcf string) string { 33 | var c kubeconfigyaml 34 | 35 | yamlFile, err := ioutil.ReadFile(kcf) 36 | if err != nil { 37 | return "" 38 | } 39 | err = yaml.Unmarshal(yamlFile, &c) 40 | if err != nil { 41 | return "" 42 | } 43 | var v string 44 | for _, cx := range c.Contexts { 45 | v = v + cx.Name + " | " 46 | } 47 | return v[:len(v)-3] 48 | } 49 | 50 | func getCurrentKubeConfig() string { 51 | if os.Getenv("KUBECONFIG") == "" { 52 | return "None" 53 | } 54 | k := strings.Split(os.Getenv("KUBECONFIG"), "/") 55 | return k[len(k)-1] 56 | } 57 | -------------------------------------------------------------------------------- /functions.go: -------------------------------------------------------------------------------- 1 | package main 2 | 3 | import ( 4 | "fmt" 5 | "io/ioutil" 6 | "log" 7 | "os" 8 | "strings" 9 | 10 | "github.com/manifoldco/promptui" 11 | ) 12 | 13 | func startNewShell(kc string) { 14 | // Get the current working directory. 15 | cwd, err := os.Getwd() 16 | if err != nil { 17 | panic(err) 18 | } 19 | 20 | // Set envvar with path to kubeconfig file 21 | os.Setenv("KUBECONFIG", kc) 22 | 23 | // Transfer stdin, stdout, and stderr to the new process 24 | // and also set target directory for the shell to start in. 25 | pa := os.ProcAttr{ 26 | Files: []*os.File{os.Stdin, os.Stdout, os.Stderr}, 27 | Dir: cwd, 28 | } 29 | 30 | // Start up a new shell. 31 | proc, err := os.StartProcess(os.Getenv("SHELL"), []string{os.Getenv("SHELL")}, &pa) 32 | if err != nil { 33 | panic(err) 34 | } 35 | 36 | // Wait until user exits the shell 37 | _, err = proc.Wait() 38 | if err != nil { 39 | panic(err) 40 | } 41 | } 42 | 43 | type kubeconfig struct { 44 | Name string 45 | Path string 46 | Context string 47 | } 48 | 49 | func selectKubeconfig() string { 50 | kcpath := getConfig() 51 | 52 | files, err := ioutil.ReadDir(kcpath) 53 | if err != nil { 54 | log.Fatal(err) 55 | } 56 | 57 | kubeconfigList := []kubeconfig{} 58 | 59 | for _, f := range files { 60 | kubeconfigList = append(kubeconfigList, kubeconfig{ 61 | f.Name(), 62 | kcpath, 63 | getKubeconfigContexts(kcpath + "/" + f.Name()), 64 | }) 65 | } 66 | 67 | templates := &promptui.SelectTemplates{ 68 | Label: "{{ . | bold }}", 69 | Active: "> {{ .Name | cyan | bold }}", 70 | Inactive: " {{ .Name }}", 71 | Details: ` 72 | {{ "Contexts: " }}{{ .Context }}`, 73 | } 74 | 75 | searcher := func(input string, index int) bool { 76 | kc := kubeconfigList[index] 77 | name := strings.Replace(strings.ToLower(kc.Name), " ", "", -1) 78 | input = strings.Replace(strings.ToLower(input), " ", "", -1) 79 | 80 | return strings.Contains(name, input) 81 | } 82 | 83 | prompt := promptui.Select{ 84 | Label: "Current Config : " + getCurrentKubeConfig(), 85 | Items: kubeconfigList, 86 | Templates: templates, 87 | Size: 10, 88 | Searcher: searcher, 89 | StartInSearchMode: true, 90 | HideSelected: true, 91 | // HideHelp: true, 92 | } 93 | 94 | i, _, err := prompt.Run() 95 | 96 | if err != nil { 97 | fmt.Printf("Prompt failed %v\n", err) 98 | return "" 99 | } 100 | 101 | return kubeconfigList[i].Path + "/" + kubeconfigList[i].Name 102 | } 103 | -------------------------------------------------------------------------------- /go.sum: -------------------------------------------------------------------------------- 1 | github.com/alecthomas/gometalinter v3.0.0+incompatible/go.mod h1:qfIpQGGz3d+NmgyPBqv+LSh50emm1pt72EtcX2vKYQk= 2 | github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= 3 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e h1:fY5BOSpyZCqRo5OhCuC+XN+r/bBCmeuuJtjz+bCNIf8= 4 | github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= 5 | github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= 6 | github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= 7 | github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= 8 | github.com/google/shlex v0.0.0-20181106134648-c34317bd91bf/go.mod h1:RpwtwJQFrIEPstU94h88MWPXP2ektJZ8cZ0YntAmXiE= 9 | github.com/gordonklaus/ineffassign v0.0.0-20180909121442-1003c8bd00dc/go.mod h1:cuNKsD1zp2v6XfE/orVX2QE1LC+i254ceGcVeDT3pTU= 10 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a h1:FaWFmfWdAUKbSCtOU2QjDaorUexogfaMgbipgYATUMU= 11 | github.com/juju/ansiterm v0.0.0-20180109212912-720a0952cc2a/go.mod h1:UJSiEoRfvx3hP73CvoARgeLjaIOjybY9vj8PUPPFGeU= 12 | github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= 13 | github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= 14 | github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= 15 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a h1:weJVJJRzAJBFRlAiJQROKQs8oC9vOxvm4rZmBBk0ONw= 16 | github.com/lunixbochs/vtclean v0.0.0-20180621232353-2d01aacdc34a/go.mod h1:pHhQNgMf3btfWnGBVipUOjRYhoOsdGqdm/+2c2E2WMI= 17 | github.com/manifoldco/promptui v0.3.3-0.20190411181407-35bab80e16a4 h1:zNtxporN4V2l47+9kRel4xPgHv+0LsRgfU3aNgKwaUs= 18 | github.com/manifoldco/promptui v0.3.3-0.20190411181407-35bab80e16a4/go.mod h1:Qr+HrTC9bwokrkg5IsBOUZYbIFuLE8wzazmgj+/aLxw= 19 | github.com/mattn/go-colorable v0.0.9 h1:UVL0vNpWh04HeJXV0KLcaT7r06gOH2l4OW6ddYRUIY4= 20 | github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= 21 | github.com/mattn/go-isatty v0.0.4 h1:bnP0vzxcAdeI1zdubAl5PjU6zsERjGZb7raWodagDYs= 22 | github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= 23 | github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= 24 | github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= 25 | github.com/tsenart/deadcode v0.0.0-20160724212837-210d2dc333e9/go.mod h1:q+QjxYvZ+fpjMXqs+XEriussHjSYqeXVnAdSV1tkMYk= 26 | golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= 27 | golang.org/x/sys v0.0.0-20181122145206-62eef0e2fa9b/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= 28 | golang.org/x/tools v0.0.0-20181122213734-04b5d21e00f1/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= 29 | gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 30 | gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= 31 | gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw= 32 | gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= 33 | --------------------------------------------------------------------------------