mirror of
https://github.com/ahmetb/kubectx.git
synced 2025-06-25 06:51:55 +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"
|
"io"
|
||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
|
|
||||||
|
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CurrentOp prints the current context
|
// CurrentOp prints the current context
|
||||||
type CurrentOp struct{}
|
type CurrentOp struct{}
|
||||||
|
|
||||||
func (_op CurrentOp) Run(stdout, _ io.Writer) error {
|
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 {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to determine kubeconfig path")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := parseKubeconfig(cfgPath)
|
v := kubeconfig.GetCurrentContext(rootNode)
|
||||||
if err != nil {
|
|
||||||
return errors.Wrap(err, "failed to read kubeconfig file")
|
|
||||||
}
|
|
||||||
|
|
||||||
v := cfg.CurrentContext
|
|
||||||
if v == "" {
|
if v == "" {
|
||||||
return errors.New("current-context is not set")
|
return errors.New("current-context is not set")
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// DeleteOp indicates intention to delete contexts.
|
// 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
|
// deleteContext deletes a context entry by NAME or current-context
|
||||||
// indicated by ".".
|
// indicated by ".".
|
||||||
func deleteContext(name string) (deleteName string, wasActiveContext bool, err error) {
|
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 {
|
if err != nil {
|
||||||
return "", false, err
|
return "", false, err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
cur := getCurrentContext(rootNode)
|
cur := kubeconfig.GetCurrentContext(rootNode)
|
||||||
|
|
||||||
// resolve "." to a real name
|
// resolve "." to a real name
|
||||||
if name == "." {
|
if name == "." {
|
||||||
@ -54,7 +57,7 @@ func deleteContext(name string) (deleteName string, wasActiveContext bool, err e
|
|||||||
if err := modifyDocToDeleteContext(rootNode, name); err != nil {
|
if err := modifyDocToDeleteContext(rootNode, name); err != nil {
|
||||||
return "", false, errors.Wrap(err, "failed to modify yaml doc")
|
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 {
|
func modifyDocToDeleteContext(rootNode *yaml.Node, deleteName string) error {
|
||||||
|
@ -10,7 +10,11 @@ import (
|
|||||||
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type defaultKubeconfigLoader struct{}
|
var (
|
||||||
|
defaultLoader kubeconfig.Loader = new(StandardKubeconfigLoader)
|
||||||
|
)
|
||||||
|
|
||||||
|
type StandardKubeconfigLoader struct{}
|
||||||
|
|
||||||
type kubeconfigFile struct { *os.File }
|
type kubeconfigFile struct { *os.File }
|
||||||
|
|
||||||
@ -22,7 +26,7 @@ func (kf *kubeconfigFile) Reset() error {
|
|||||||
return errors.Wrap(err, "failed to seek in file")
|
return errors.Wrap(err, "failed to seek in file")
|
||||||
}
|
}
|
||||||
|
|
||||||
func (defaultKubeconfigLoader) Load() (kubeconfig.ReadWriteResetCloser, error) {
|
func (*StandardKubeconfigLoader) Load() (kubeconfig.ReadWriteResetCloser, error) {
|
||||||
cfgPath, err := kubeconfigPath()
|
cfgPath, err := kubeconfigPath()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, errors.Wrap(err, "cannot determine kubeconfig path")
|
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
|
rootNode *yaml.Node
|
||||||
}
|
}
|
||||||
|
|
||||||
func (k *Kubeconfig) WithLoader(l Loader) {
|
func (k *Kubeconfig) WithLoader(l Loader) *Kubeconfig {
|
||||||
k.loader = l
|
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) {
|
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"
|
"facette.io/natsort"
|
||||||
"github.com/fatih/color"
|
"github.com/fatih/color"
|
||||||
"github.com/pkg/errors"
|
|
||||||
|
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
type context struct {
|
type context struct {
|
||||||
@ -22,30 +23,23 @@ type kubeconfigContents struct {
|
|||||||
// ListOp describes listing contexts.
|
// ListOp describes listing contexts.
|
||||||
type ListOp struct{}
|
type ListOp struct{}
|
||||||
|
|
||||||
func (_ ListOp) Run(stdout, stderr io.Writer) error {
|
func (_ ListOp) Run(stdout, _ io.Writer) error {
|
||||||
// TODO extract printing and sorting into a function that's testable
|
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||||
|
defer kc.Close()
|
||||||
cfgPath, err := kubeconfigPath()
|
rootNode, err := kc.ParseRaw()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.Wrap(err, "failed to determine kubeconfig path")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
cfg, err := parseKubeconfig(cfgPath)
|
ctxs := kubeconfig.ContextNames(rootNode)
|
||||||
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)
|
|
||||||
}
|
|
||||||
natsort.Sort(ctxs)
|
natsort.Sort(ctxs)
|
||||||
|
|
||||||
// TODO support KUBECTX_CURRENT_FGCOLOR
|
// TODO support KUBECTX_CURRENT_FGCOLOR
|
||||||
// TODO support KUBECTX_CURRENT_BGCOLOR
|
// TODO support KUBECTX_CURRENT_BGCOLOR
|
||||||
|
cur := kubeconfig.GetCurrentContext(rootNode)
|
||||||
for _, c := range ctxs {
|
for _, c := range ctxs {
|
||||||
s := c
|
s := c
|
||||||
if c == cfg.CurrentContext {
|
if c == cur {
|
||||||
s = color.New(color.FgGreen, color.Bold).Sprint(c)
|
s = color.New(color.FgGreen, color.Bold).Sprint(c)
|
||||||
}
|
}
|
||||||
fmt.Fprintf(stdout, "%s\n", s)
|
fmt.Fprintf(stdout, "%s\n", s)
|
||||||
|
@ -6,6 +6,8 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// RenameOp indicates intention to rename contexts.
|
// 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,
|
// to the "new" value. If the old refers to the current-context,
|
||||||
// current-context preference is also updated.
|
// current-context preference is also updated.
|
||||||
func (op RenameOp) Run(_, stderr io.Writer) error {
|
func (op RenameOp) Run(_, stderr io.Writer) error {
|
||||||
f, rootNode, err := openKubeconfig()
|
kc := new(kubeconfig.Kubeconfig).WithLoader(defaultLoader)
|
||||||
if err != nil {
|
defer kc.Close()
|
||||||
return nil
|
|
||||||
}
|
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
cur := getCurrentContext(rootNode)
|
rootNode, err := kc.ParseRaw()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
cur := kubeconfig.GetCurrentContext(rootNode)
|
||||||
if op.Old == "." {
|
if op.Old == "." {
|
||||||
op.Old = cur
|
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")
|
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")
|
return errors.Wrap(err, "failed to save modified kubeconfig")
|
||||||
}
|
}
|
||||||
printSuccess(stderr, "Context %q renamed to %q.", op.Old, op.New)
|
printSuccess(stderr, "Context %q renamed to %q.", op.Old, op.New)
|
||||||
|
@ -5,6 +5,8 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// SwitchOp indicates intention to switch contexts.
|
// SwitchOp indicates intention to switch contexts.
|
||||||
@ -33,20 +35,23 @@ func switchContext(name string) (string, error) {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return "", errors.Wrap(err, "failed to determine state file")
|
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 {
|
if err != nil {
|
||||||
return "", err
|
return "", err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
prev := getCurrentContext(kc)
|
prev := kubeconfig.GetCurrentContext(rootNode)
|
||||||
if !checkContextExists(kc, name) {
|
if !checkContextExists(rootNode, name) {
|
||||||
return "", errors.Errorf("no context exists with the name: %q", 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
|
return "", err
|
||||||
}
|
}
|
||||||
if err := saveKubeconfigRaw(f, kc); err != nil {
|
if err := kc.Save(); err != nil {
|
||||||
return "", errors.Wrap(err, "failed to save kubeconfig")
|
return "", errors.Wrap(err, "failed to save kubeconfig")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -77,22 +82,7 @@ func swapContext() (string, error) {
|
|||||||
|
|
||||||
|
|
||||||
func checkContextExists(rootNode *yaml.Node, name string) bool {
|
func checkContextExists(rootNode *yaml.Node, name string) bool {
|
||||||
contexts := valueOf(rootNode, "contexts")
|
ctxNames := kubeconfig.ContextNames(rootNode)
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
for _, v := range ctxNames {
|
for _, v := range ctxNames {
|
||||||
if v == name {
|
if v == name {
|
||||||
return true
|
return true
|
||||||
@ -101,6 +91,7 @@ func checkContextExists(rootNode *yaml.Node, name string) bool {
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO delete
|
||||||
func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
|
func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
|
||||||
if mapNode.Kind != yaml.MappingNode {
|
if mapNode.Kind != yaml.MappingNode {
|
||||||
return nil
|
return nil
|
||||||
@ -113,18 +104,7 @@ func valueOf(mapNode *yaml.Node, key string) *yaml.Node {
|
|||||||
return nil
|
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 {
|
func modifyCurrentContext(rootNode *yaml.Node, name string) error {
|
||||||
if rootNode.Kind != yaml.MappingNode {
|
if rootNode.Kind != yaml.MappingNode {
|
||||||
|
@ -6,22 +6,26 @@ import (
|
|||||||
|
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"gopkg.in/yaml.v3"
|
"gopkg.in/yaml.v3"
|
||||||
|
|
||||||
|
"github.com/ahmetb/kubectx/cmd/kubectx/kubeconfig"
|
||||||
)
|
)
|
||||||
|
|
||||||
// UnsetOp indicates intention to remove current-context preference.
|
// UnsetOp indicates intention to remove current-context preference.
|
||||||
type UnsetOp struct{}
|
type UnsetOp struct{}
|
||||||
|
|
||||||
func (_ UnsetOp) Run(_, stderr io.Writer) error {
|
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 {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
defer f.Close()
|
|
||||||
|
|
||||||
if err := modifyDocToUnsetContext(rootNode); err != nil {
|
if err := modifyDocToUnsetContext(rootNode); err != nil {
|
||||||
return errors.Wrap(err, "error while modifying current-context")
|
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")
|
return errors.Wrap(err, "failed to save kubeconfig file after modification")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user