mirror of
https://github.com/ahmetb/kubectx.git
synced 2025-06-24 06:27:33 +00:00
Use kubeconfig pkg for parsing utils
Signed-off-by: Ahmet Alp Balkan <ahmetb@google.com>
This commit is contained in:
parent
94664bcaf9
commit
1313d98f57
@ -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")
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
|
47
cmd/kubectx/kubeconfig/helpers.go
Normal file
47
cmd/kubectx/kubeconfig/helpers.go
Normal 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
|
||||
}
|
@ -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) {
|
||||
|
@ -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
|
||||
}
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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")
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user