Skip to content
This repository has been archived by the owner on May 3, 2020. It is now read-only.

Commit

Permalink
add kubectx
Browse files Browse the repository at this point in the history
  • Loading branch information
aca committed Jan 24, 2020
1 parent 51bbe06 commit 533f1ef
Show file tree
Hide file tree
Showing 8 changed files with 385 additions and 35 deletions.
21 changes: 15 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,22 @@
# kubectx

Faster/Smarter alternative to famous [kubectx](https://github.com/ahmetb/kubectx). <br>
Uses client-go.
Simply 5x-10x faster alternative to famous [kubectx](https://github.com/ahmetb/kubectx). Uses client-go.

### Install
```
# kubens
go get -u github.com/aca/kubectx/cmd/kubens
# kubectx
TODO
go get -u github.com/aca/kubectx/cmd/kubectx
```

### Benchmarks

| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `/usr/bin/kubectx minikube` | 98.9 ± 12.1 | 84.8 | 138.6 | 5.35 ± 0.89 |
| `/home/rok/bin/kubectx minikube` | 18.5 ± 2.1 | 15.6 | 27.1 | 1.00 |


| Command | Mean [ms] | Min [ms] | Max [ms] | Relative |
|:---|---:|---:|---:|---:|
| `/usr/bin/kubens kube-system` | 258.6 ± 20.7 | 237.9 | 300.9 | 5.73 ± 1.78 |
| `/home/rok/bin/kubens kube-system` | 45.1 ± 13.6 | 32.3 | 120.0 | 1.00 |
23 changes: 23 additions & 0 deletions benchmark.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
#!/bin/sh

kubectx=/usr/bin/kubectx
go_kubectx="$GOPATH/bin/kubectx"

kubens=/usr/bin/kubens
go_kubens="$GOPATH/bin/kubens"

bench_kubectx() {
echo "COMMAND: kubectx $@"
hyperfine "$kubectx $@" "$go_kubectx $@" --export-markdown bench_kubectx.md 2>/dev/null
printf "\n\n"
}

bench_kubens() {
echo "COMMAND: kubens $@"
hyperfine "$kubens $@" "$go_kubens $@" --export-markdown bench_kubens.md 2>/dev/null
printf "\n\n"
}

bench_kubectx minikube
bench_kubens kube-system

120 changes: 120 additions & 0 deletions cmd/kubectx/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
package main

import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"

"github.com/aca/kubectx/config"
"github.com/aca/kubectx/fzfutil"
"github.com/spf13/pflag"

clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

"k8s.io/client-go/tools/clientcmd"
)

var cfg *config.Config
var rules *clientcmd.ClientConfigLoadingRules
var clientcmdCfg *clientcmdapi.Config

func main() {
var err error
log.SetFlags(log.LstdFlags | log.Lshortfile)
cfg, err = config.ReadCfg()
if err != nil {
log.Fatal(err)
}

fs := pflag.NewFlagSet("kubectx", pflag.ContinueOnError)
current := fs.BoolP("current", "c", false, "show the current context")
help := fs.BoolP("help", "h", false, "help")
fs.Parse(os.Args[1:])

if *help {
fmt.Printf(`USAGE:
kubectx : switch context(interactive)
kubectx <QUERY> : switch context which closest match with QUERY(if failed to match, interactive mode)
kubectx - : switch to previous context (~/.config/kubectx/config.json)
kubectx -c, --current : show current context name
kubectx -h,--help : help
`)
return
}

rules = clientcmd.NewDefaultClientConfigLoadingRules()
b, err := ioutil.ReadFile(rules.GetDefaultFilename())
if err != nil {
log.Fatal(err)
}

clientcmdCfg, err = clientcmd.Load(b)
if err != nil {
log.Fatal(err)
}

var query string

if *current {
fmt.Println(clientcmdCfg.CurrentContext)
return
}

if len(os.Args) > 1 && os.Args[1] == "-" {
query = cfg.LastContext
} else if len(os.Args) > 1 {
query = os.Args[1]
}

err = kubectx(query)
if err != nil {
fmt.Printf("Err: %s", err)
os.Exit(1)
}
}

func kubectx(query string) error {
var err error

_, ok := clientcmdCfg.Contexts[query]
if ok {
return modifyConfig(query)
}

listNS := func(in io.WriteCloser) {
for key, _ := range clientcmdCfg.Contexts {
fmt.Fprintln(in, key)
}
}

fzfopt := []string{"--select-1", "--query", query}

result, err := fzfutil.FZF(listNS, fzfopt...)
if err != nil {
return err
}

if len(result) == 0 {
return errors.New("no context selected")
}

return modifyConfig(result[0])
}

func modifyConfig(to string) (err error) {
curCtx := clientcmdCfg.CurrentContext
defer func() {
if err == nil {
if curCtx != to {
cfg.LastContext = curCtx
config.WriteCfg(cfg)
}
fmt.Printf("Switched to context \"%s\"\n", to)
}
}()
clientcmdCfg.CurrentContext = to
return clientcmd.ModifyConfig(rules, *clientcmdCfg, false)
}
109 changes: 81 additions & 28 deletions cmd/kubens/main.go
Original file line number Diff line number Diff line change
@@ -1,79 +1,132 @@
package main

import (
"errors"
"fmt"
"io"
"io/ioutil"
"log"
"os"
"strings"
"time"

"github.com/aca/kubectx/config"
"github.com/aca/kubectx/fzfutil"
"github.com/spf13/pflag"

metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
clientcmdapi "k8s.io/client-go/tools/clientcmd/api"

"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
)

var cfg *config.Config
var rules *clientcmd.ClientConfigLoadingRules
var clientcmdCfg *clientcmdapi.Config
var kubeCfgBytes []byte

func main() {
var err error
log.SetFlags(log.LstdFlags | log.Lshortfile)
cfg, err = config.ReadCfg()
if err != nil {
log.Fatal(err)
}

fs := pflag.NewFlagSet("kubectx", pflag.ContinueOnError)
current := fs.BoolP("current", "c", false, "show the current context")
help := fs.BoolP("help", "h", false, "help")
fs.Parse(os.Args[1:])

if *help {
fmt.Printf(`USAGE:
kubens : switch namespace(interactive)
kubens <QUERY> : switch namespace which closest match with QUERY(if failed to match, interactive mode)
kubens - : switch to previous namespace (~/.config/kubectx/config.json)
kubens -c, --current : show current namespace name
kubens -h,--help : help
`)
return
}

rules = clientcmd.NewDefaultClientConfigLoadingRules()
kubeCfgBytes, err = ioutil.ReadFile(rules.GetDefaultFilename())
if err != nil {
log.Fatal(err)
}

clientcmdCfg, err = clientcmd.Load(kubeCfgBytes)
if err != nil {
log.Fatal(err)
}

var query string
if len(os.Args) > 1 {
query = strings.Join(os.Args[1:], "")

if *current {
fmt.Println(clientcmdCfg.Contexts[clientcmdCfg.CurrentContext].Namespace)
return
}

if len(os.Args) > 1 && os.Args[1] == "-" {
query = cfg.LastNamespace
} else if len(os.Args) > 1 {
query = os.Args[1]
}

err := kubens(query)
err = kubens(query)
if err != nil {
fmt.Printf("Err: %s", err)
log.Printf("Err: %s", err)
os.Exit(1)
}
}

// kubens fuzzy finds & switches namespaces
func kubens(query string) error {
rules := clientcmd.NewDefaultClientConfigLoadingRules()

kubeconfig, err := ioutil.ReadFile(rules.GetDefaultFilename())
func kubens(query string) (err error) {
restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeCfgBytes)
if err != nil {
return err
}
restConfig, err := clientcmd.RESTConfigFromKubeConfig(kubeconfig)
restConfig.Timeout = time.Second * 5

clientset, err := kubernetes.NewForConfig(restConfig)
if err != nil {
return err
}

fzfopt := []string{"--select-1", "--query", query}
namespaces, err := clientset.CoreV1().Namespaces().List(metav1.ListOptions{})
if err != nil {
return err
}

output, err := fzfutil.FZF(func(in io.WriteCloser) {
namespaces, err := clientset.CoreV1().Namespaces().List(metav1.ListOptions{})
if err != nil {
panic(err)
}
fzfopt := []string{"--select-1", "--query", query}

inputChan := make(chan string, 100)
result, err := fzfutil.FZF(func(in io.WriteCloser) {
for _, item := range namespaces.Items {
fmt.Fprintln(in, item.ObjectMeta.Name)
}
close(inputChan)

}, fzfopt...)

if err != nil {
return err
}

if len(output) == 0 {
return fmt.Errorf("namespace not selected")
if len(result) == 0 {
return errors.New("no namespace selected")
}

clientcmdConfig, err := rules.Load()
if err != nil {
return err
}
return modifyConfig(result[0])
}

clientcmdConfig.Contexts[clientcmdConfig.CurrentContext].Namespace = output[0]
return clientcmd.ModifyConfig(rules, *clientcmdConfig, false)
func modifyConfig(to string) (err error) {
curNS := clientcmdCfg.Contexts[clientcmdCfg.CurrentContext].Namespace
defer func() {
if err == nil {
if curNS != to {
cfg.LastNamespace = curNS
config.WriteCfg(cfg)
}
fmt.Printf("Switched to namespace \"%s\"\n", to)
}
}()
clientcmdCfg.Contexts[clientcmdCfg.CurrentContext].Namespace = to
return clientcmd.ModifyConfig(rules, *clientcmdCfg, false)
}
Loading

0 comments on commit 533f1ef

Please sign in to comment.