Implement switch via editing yaml in-place

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
Ahmet Alp Balkan 2020-04-10 14:07:12 -07:00
parent 04e963c02c
commit a9476f3215
No known key found for this signature in database
GPG Key ID: 441833503E604E2C
2 changed files with 118 additions and 5 deletions

View File

@ -20,15 +20,25 @@ func main() {
case ListOp:
printListContexts(os.Stdout)
case SwitchOp:
// TODO implement
panic("not implemented")
if v.Target == "-" {
// TODO implement swap
panic("not implemented")
}
newCtx, err := switchContext(v.Target)
if err != nil {
printError("faield to switch context: %v", err)
os.Exit(1)
}
fmt.Fprintf(os.Stderr, "Switched to context %q.\n", newCtx)
case UnknownOp:
fmt.Printf("%s unsupported operation: %s\n",
color.RedString("error:"),
strings.Join(v.Args, " "))
printError("unsupported operation: %s", strings.Join(v.Args, " "))
printHelp(os.Stdout)
os.Exit(1)
default:
fmt.Printf("internal error: operation type %T not handled", op)
}
}
func printError(format string, args ...interface{}) {
fmt.Fprintf(os.Stderr, color.RedString("error: "+format+"\n"), args...)
}

103
cmd/kubectx/switch.go Normal file
View File

@ -0,0 +1,103 @@
package main
import (
"fmt"
"io"
"os"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)
// switchContext switches to specified context name.
func switchContext(name string) (string, error) {
cfgPath, err := kubeconfigPath()
if err != nil {
return "", errors.Wrap(err, "cannot determine kubeconfig path")
}
f, err := os.OpenFile(cfgPath, os.O_RDWR, 0)
if err != nil {
return "", errors.Wrap(err, "failed to open file")
}
defer f.Close()
kc, err := parseKubeconfigRaw(f)
if err != nil {
return "", errors.Wrap(err, "yaml parse error")
}
cur := getCurrentContext(kc)
fmt.Printf("current-context=%s\n", cur)
if err := modifyCurrentContext(kc, name); err != nil {
return "", err
}
if err := f.Truncate(0); err != nil {
return "", errors.Wrap(err, "failed to truncate")
}
if _, err := f.Seek(0, 0); err != nil {
return "", errors.Wrap(err, "failed to seek")
}
if err := saveKubeconfigRaw(f, kc); err != nil {
return "", errors.Wrap(err, "failed to save kubeconfig")
}
return name, nil
}
// getCurrentContext returns "current-context" value in given
// kubeconfig object Node, or returns "" if not found.
func getCurrentContext(rootNode *yaml.Node) string {
if rootNode.Kind != yaml.MappingNode {
return ""
}
for i, ch := range rootNode.Content {
if i%2 == 0 && ch.Value == "current-context" {
return rootNode.Content[i+1].Value
}
}
return ""
}
func modifyCurrentContext(rootNode *yaml.Node, name string) error {
if rootNode.Kind != yaml.MappingNode {
return errors.New("document is not a map")
}
// find current-context field => modify value (next children)
for i, ch := range rootNode.Content {
if i%2 == 0 && ch.Value == "current-context" {
rootNode.Content[i+1].Value = name
return nil
}
}
// if current-context ==> create new field
keyNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: "current-context",
Tag: "!!str"}
valueNode := &yaml.Node{
Kind: yaml.ScalarNode,
Value: name,
Tag: "!!str"}
rootNode.Content = append(rootNode.Content, keyNode, valueNode)
return nil
}
func parseKubeconfigRaw(r io.Reader) (*yaml.Node, error) {
var v yaml.Node
if err := yaml.NewDecoder(r).Decode(&v); err != nil {
return nil, err
}
return v.Content[0], nil
}
func saveKubeconfigRaw(w io.Writer, rootNode *yaml.Node) error {
enc := yaml.NewEncoder(w)
enc.SetIndent(2)
return enc.Encode(rootNode)
}