Use kubeconfig pkg for parsing utils

Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
Ahmet Alp Balkan 2020-04-12 13:17:08 -07:00
parent 94664bcaf9
commit 1313d98f57
No known key found for this signature in database
GPG Key ID: 441833503E604E2C
10 changed files with 118 additions and 125 deletions

View File

@ -5,23 +5,22 @@ import (
"io"
"github.com/pkg/errors"
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
)
// CurrentOp prints the current context
type CurrentOp struct{}
func (_op CurrentOp) Run(stdout, _ io.Writer) error {
cfgPath, err := kubeconfigPath()
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close()
rootNode, err := kc.ParseRaw()
if err != nil {
return errors.Wrap(err, "failed to determine kubeconfig path")
return err
}
cfg, err := parseKubeconfig(cfgPath)
if err != nil {
return errors.Wrap(err, "failed to read kubeconfig file")
}
v := cfg.CurrentContext
v := kubeconfig.GetCurrentContext(rootNode)
if v == "" {
return errors.New("current-context is not set")
}

View File

@ -5,6 +5,8 @@ import (
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
)
// DeleteOp indicates intention to delete contexts.
@ -33,13 +35,14 @@ func (op DeleteOp) Run(_, stderr io.Writer) error {
// deleteContext deletes a context entry by NAME or current-context
// indicated by ".".
func deleteContext(name string) (deleteName string, wasActiveContext bool, err error) {
f, rootNode, err := openKubeconfig()
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close()
rootNode, err := kc.ParseRaw()
if err != nil {
return "", false, err
}
defer f.Close()
cur := getCurrentContext(rootNode)
cur := kubeconfig.GetCurrentContext(rootNode)
// resolve "." to a real name
if name == "." {
@ -54,7 +57,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e
if err := modifyDocToDeleteContext(rootNode, name); err != nil {
return "", false, errors.Wrap(err, "failed to modify yaml doc")
}
return name, wasActiveContext, errors.Wrap(saveKubeconfigRaw(f, rootNode), "failed to save kubeconfig file")
return name, wasActiveContext, errors.Wrap(kc.Save(), "failed to save kubeconfig file")
}
func modifyDocToDeleteContext(rootNode *yaml.Node, deleteName string) error {

View File

@ -10,7 +10,11 @@ import (
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
)
type defaultKubeconfigLoader struct{}
var (
defaultLoader kubeconfig.Loader = new(StandardKubeconfigLoader)
)
type StandardKubeconfigLoader struct{}
type kubeconfigFile struct { *os.File }
@ -22,7 +26,7 @@ func (kf *kubeconfigFile) Reset() error {
return errors.Wrap(err, "failed to seek in file")
}
func (defaultKubeconfigLoader) Load() (kubeconfig.ReadWriteResetCloser, error) {
func (*StandardKubeconfigLoader) Load() (kubeconfig.ReadWriteResetCloser, error) {
cfgPath, err := kubeconfigPath()
if err != nil {
return nil, errors.Wrap(err, "cannot determine kubeconfig path")

View File

@ -0,0 +1,47 @@
package kubeconfig
import "gopkg.in/yaml.v3"
func ContextNames(rootNode *yaml.Node) []string {
contexts := valueOf(rootNode, "contexts")
if contexts == nil {
return nil
}
if contexts.Kind != yaml.SequenceNode {
return nil
}
var ctxNames []string
for _, ctx := range contexts.Content {
nameVal := valueOf(ctx, "name")
if nameVal != nil {
ctxNames = append(ctxNames, nameVal.Value)
}
}
return ctxNames
}
// 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 ""
}
v := valueOf(rootNode, "current-context")
if v == nil {
return ""
}
return v.Value
}
func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
if mapNode.Kind != yaml.MappingNode {
return nil
}
for i, ch := range mapNode.Content {
if i%2 == 0 && ch.Kind == yaml.ScalarNode && ch.Value == key {
return mapNode.Content[i+1]
}
}
return nil
}

View File

@ -25,8 +25,16 @@ type Kubeconfig struct {
rootNode *yaml.Node
}
func (k *Kubeconfig) WithLoader(l Loader) {
func (k *Kubeconfig) WithLoader(l Loader) *Kubeconfig {
k.loader = l
return k
}
func (k *Kubeconfig) Close() error {
if k.f == nil {
return nil
}
return k.f.Close()
}
func (k *Kubeconfig) ParseRaw() (*yaml.Node, error) {

View File

@ -1,50 +0,0 @@
package main
import (
"io"
"os"
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
)
func parseKubeconfigRaw(r io.Reader) (*yaml.Node, error) {
// TODO DELETE
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 {
if f, ok := w.(*os.File); ok {
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")
}
}
enc := yaml.NewEncoder(w)
enc.SetIndent(2)
return enc.Encode(rootNode)
}
func openKubeconfig() (f *os.File, rootNode *yaml.Node, err error) {
cfgPath, err := kubeconfigPath()
if err != nil {
return nil, nil, errors.Wrap(err, "cannot determine kubeconfig path")
}
f, err = os.OpenFile(cfgPath, os.O_RDWR, 0)
if err != nil {
return nil, nil, errors.Wrap(err, "failed to open file")
}
kc, err := parseKubeconfigRaw(f)
if err != nil {
f.Close()
return nil, nil, errors.Wrap(err, "yaml parse error")
}
return f, kc, nil
}

View File

@ -6,7 +6,8 @@ import (
"facette.io/natsort"
"github.com/fatih/color"
"github.com/pkg/errors"
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
)
type context struct {
@ -22,30 +23,23 @@ type kubeconfigContents struct {
// ListOp describes listing contexts.
type ListOp struct{}
func (_ ListOp) Run(stdout, stderr io.Writer) error {
// TODO extract printing and sorting into a function that's testable
cfgPath, err := kubeconfigPath()
func (_ ListOp) Run(stdout, _ io.Writer) error {
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close()
rootNode, err := kc.ParseRaw()
if err != nil {
return errors.Wrap(err, "failed to determine kubeconfig path")
return err
}
cfg, err := parseKubeconfig(cfgPath)
if err != nil {
return errors.Wrap(err, "failed to read kubeconfig file")
}
ctxs := make([]string, 0, len(cfg.Contexts))
for _, c := range cfg.Contexts {
ctxs = append(ctxs, c.Name)
}
ctxs := kubeconfig.ContextNames(rootNode)
natsort.Sort(ctxs)
// TODO support KUBECTX_CURRENT_FGCOLOR
// TODO support KUBECTX_CURRENT_BGCOLOR
cur := kubeconfig.GetCurrentContext(rootNode)
for _, c := range ctxs {
s := c
if c == cfg.CurrentContext {
if c == cur {
s = color.New(color.FgGreen, color.Bold).Sprint(c)
}
fmt.Fprintf(stdout, "%s\n", s)

View File

@ -6,6 +6,8 @@ import (
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
)
// RenameOp indicates intention to rename contexts.
@ -32,13 +34,15 @@ func parseRenameSyntax(v string) (string, string, bool) {
// to the "new" value. If the old refers to the current-context,
// current-context preference is also updated.
func (op RenameOp) Run(_, stderr io.Writer) error {
f, rootNode, err := openKubeconfig()
if err != nil {
return nil
}
defer f.Close()
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close()
cur := getCurrentContext(rootNode)
rootNode, err := kc.ParseRaw()
if err != nil {
return err
}
cur := kubeconfig.GetCurrentContext(rootNode)
if op.Old == "." {
op.Old = cur
}
@ -62,7 +66,7 @@ func (op RenameOp) Run(_, stderr io.Writer) error {
return errors.Wrap(err, "failed to set current-context to new name")
}
}
if err := saveKubeconfigRaw(f, rootNode); err != nil {
if err := kc.Save(); err != nil {
return errors.Wrap(err, "failed to save modified kubeconfig")
}
printSuccess(stderr, "Context %q renamed to %q.", op.Old, op.New)

View File

@ -5,6 +5,8 @@ import (
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
)
// SwitchOp indicates intention to switch contexts.
@ -33,20 +35,23 @@ func switchContext(name string) (string, error) {
if err != nil {
return "", errors.Wrap(err, "failed to determine state file")
}
f, kc, err := openKubeconfig()
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close()
rootNode, err := kc.ParseRaw()
if err != nil {
return "", err
}
defer f.Close()
prev := getCurrentContext(kc)
if !checkContextExists(kc, name) {
prev := kubeconfig.GetCurrentContext(rootNode)
if !checkContextExists(rootNode, name) {
return "", errors.Errorf("no context exists with the name: %q", name)
}
if err := modifyCurrentContext(kc, name); err != nil {
if err := modifyCurrentContext(rootNode, name); err != nil {
return "", err
}
if err := saveKubeconfigRaw(f, kc); err != nil {
if err := kc.Save(); err != nil {
return "", errors.Wrap(err, "failed to save kubeconfig")
}
@ -77,22 +82,7 @@ func swapContext() (string, error) {
func checkContextExists(rootNode *yaml.Node, name string) bool {
contexts := valueOf(rootNode, "contexts")
if contexts == nil {
return false
}
if contexts.Kind != yaml.SequenceNode {
return false
}
var ctxNames []string
for _, ctx := range contexts.Content {
nameVal := valueOf(ctx, "name")
if nameVal != nil {
ctxNames = append(ctxNames, nameVal.Value)
}
}
ctxNames := kubeconfig.ContextNames(rootNode)
for _, v := range ctxNames {
if v == name {
return true
@ -101,6 +91,7 @@ func checkContextExists(rootNode *yaml.Node, name string) bool {
return false
}
// TODO delete
func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
if mapNode.Kind != yaml.MappingNode {
return nil
@ -113,18 +104,7 @@ func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
return 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 ""
}
v := valueOf(rootNode, "current-context")
if v == nil {
return ""
}
return v.Value
}
func modifyCurrentContext(rootNode *yaml.Node, name string) error {
if rootNode.Kind != yaml.MappingNode {

View File

@ -6,22 +6,26 @@ import (
"github.com/pkg/errors"
"gopkg.in/yaml.v3"
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
)
// UnsetOp indicates intention to remove current-context preference.
type UnsetOp struct{}
func (_ UnsetOp) Run(_, stderr io.Writer) error {
f, rootNode, err := openKubeconfig()
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
defer kc.Close()
rootNode, err := kc.ParseRaw()
if err != nil {
return err
}
defer f.Close()
if err := modifyDocToUnsetContext(rootNode); err != nil {
return errors.Wrap(err, "error while modifying current-context")
}
if err := saveKubeconfigRaw(f, rootNode); err != nil {
if err := kc.Save(); err != nil {
return errors.Wrap(err, "failed to save kubeconfig file after modification")
}