posh-providers/etcd-io/etcd/command.go
2023-05-25 10:03:43 +02:00

211 lines
5.1 KiB
Go

package etcd
import (
"bytes"
"context"
"os"
"os/exec"
"path"
"strings"
prompt2 "github.com/c-bata/go-prompt"
"github.com/foomo/posh-providers/kubernets/kubectl"
"github.com/foomo/posh/pkg/command/tree"
"github.com/foomo/posh/pkg/env"
"github.com/foomo/posh/pkg/log"
"github.com/foomo/posh/pkg/prompt/goprompt"
"github.com/foomo/posh/pkg/readline"
"github.com/foomo/posh/pkg/util/prints"
"github.com/foomo/posh/pkg/util/suggests"
"github.com/pkg/errors"
)
type Command struct {
l log.Logger
etcd *ETCD
kubectl *kubectl.Kubectl
commandTree tree.Root
}
// ------------------------------------------------------------------------------------------------
// ~ Constructor
// ------------------------------------------------------------------------------------------------
func NewCommand(l log.Logger, etcd *ETCD, kubectl *kubectl.Kubectl, opts ...Option) *Command {
inst := &Command{
l: l.Named("etcd"),
etcd: etcd,
kubectl: kubectl,
}
args := tree.Args{
{
Name: "path",
Suggest: func(ctx context.Context, t tree.Root, r *readline.Readline) []prompt2.Suggest {
if value, ok := inst.etcd.cfg.Cluster(r.Args().At(0)); ok {
return suggests.List(value.Paths)
}
return nil
},
},
}
flags := func(ctx context.Context, r *readline.Readline, fs *readline.FlagSets) error {
if r.Args().HasIndex(0) {
fs.Internal().String("profile", "", "Profile to use.")
if err := fs.Internal().SetValues("profile", inst.kubectl.Cluster(r.Args().At(0)).Profiles(ctx)...); err != nil {
return err
}
}
return nil
}
inst.commandTree = tree.New(&tree.Node{
Name: "etcd",
Description: "Read and write to etcd",
Nodes: tree.Nodes{
{
Name: "cluster",
Values: func(ctx context.Context, r *readline.Readline) []goprompt.Suggest {
var ret []string
for _, cluster := range inst.etcd.kubectl.Clusters() {
if _, ok := inst.etcd.cfg.Cluster(cluster.Name()); ok {
ret = append(ret, cluster.Name())
}
}
return suggests.List(ret)
},
Nodes: tree.Nodes{
{
Name: "get",
Args: args,
Flags: flags,
Execute: inst.get,
},
{
Name: "edit",
Args: args,
Flags: flags,
Execute: inst.edit,
},
},
},
},
})
return inst
}
// ------------------------------------------------------------------------------------------------
// ~ Public methods
// ------------------------------------------------------------------------------------------------
func (c *Command) Name() string {
return c.commandTree.Node().Name
}
func (c *Command) Description() string {
return c.commandTree.Node().Description
}
func (c *Command) Complete(ctx context.Context, r *readline.Readline) []goprompt.Suggest {
return c.commandTree.Complete(ctx, r)
}
func (c *Command) Execute(ctx context.Context, r *readline.Readline) error {
return c.commandTree.Execute(ctx, r)
}
func (c *Command) Help(ctx context.Context, r *readline.Readline) string {
return c.commandTree.Help(ctx, r)
}
// ------------------------------------------------------------------------------------------------
// ~ Private methods
// ------------------------------------------------------------------------------------------------
func (c *Command) get(ctx context.Context, r *readline.Readline) error {
etcdPath := r.Args().At(2)
ifs := r.FlagSets().Internal()
profile, err := ifs.GetString("profile")
if err != nil {
return err
}
if cluster, ok := c.etcd.cfg.Cluster(r.Args().At(0)); !ok {
return errors.New("invalid cluster")
} else if out, err := c.etcd.GetPath(ctx, cluster, profile, etcdPath); err != nil {
return errors.Wrap(err, out)
} else {
prints.Code(c.l, etcdPath, out+"\n", "yaml")
}
return nil
}
func (c *Command) edit(ctx context.Context, r *readline.Readline) error {
var (
prev []byte
next []byte
)
cluster, ok := c.etcd.cfg.Cluster(r.Args().At(0))
if !ok {
return errors.New("invalid cluster")
}
etcdPath := r.Args().At(2)
ifs := r.FlagSets().Internal()
filename := env.Path(c.etcd.cfg.ConfigPath, etcdPath)
profile, err := ifs.GetString("profile")
if err != nil {
return err
}
{ // retrieve data
if value, err := c.etcd.GetPath(ctx, cluster, profile, etcdPath); err != nil {
return err
} else {
prev = []byte(strings.ReplaceAll(value, "\r\r\n", "\n"))
}
}
{ // write to file
if err := os.MkdirAll(path.Dir(filename), 0700); err != nil {
return err
} else if err := os.WriteFile(filename, prev, 0600); err != nil {
return err
}
}
{ // edit file
d := "vim"
if value := os.Getenv("EDITOR"); value != "" {
d = value
}
editor := exec.Command(d, filename)
editor.Stdin = os.Stdin
editor.Stdout = os.Stdout
editor.Stderr = os.Stderr
if err := editor.Run(); err != nil {
return err
}
}
{ // read in file
if value, err := os.ReadFile(filename); err != nil {
return err
} else if bytes.Equal(prev, value) {
c.l.Info("no changes")
return nil
} else {
next = value
}
}
c.l.Info("updating config")
if out, err := c.etcd.SetPath(ctx, cluster, profile, etcdPath, string(next)); err != nil {
return errors.Wrap(err, out)
}
return nil
}