k9s/internal/view/exec.go

104 lines
1.9 KiB
Go

package view
import (
"context"
"errors"
"fmt"
"os"
"os/exec"
"os/signal"
"strings"
"syscall"
"github.com/rs/zerolog/log"
)
const (
shellCheck = `command -v bash >/dev/null && exec bash || exec sh`
bannerFmt = "<<K9s-Shell>> Pod: %s | Container: %s \n"
)
type shellOpts struct {
clear, background bool
binary string
banner string
args []string
}
func runK(app *App, opts shellOpts) bool {
bin, err := exec.LookPath("kubectl")
if err != nil {
log.Error().Msgf("Unable to find kubectl command in path %v", err)
return false
}
opts.binary, opts.background = bin, false
return run(app, opts)
}
func run(app *App, opts shellOpts) bool {
app.Halt()
defer app.Resume()
return app.Suspend(func() {
if err := execute(opts); err != nil {
app.Flash().Errf("Command exited: %v", err)
}
})
}
func edit(app *App, opts shellOpts) bool {
bin, err := exec.LookPath(os.Getenv("EDITOR"))
if err != nil {
log.Error().Msgf("Unable to find editor command in path %v", err)
return false
}
opts.binary, opts.background = bin, false
return run(app, opts)
}
func execute(opts shellOpts) error {
if opts.clear {
clearScreen()
}
ctx, cancel := context.WithCancel(context.Background())
defer func() {
cancel()
clearScreen()
}()
sigChan := make(chan os.Signal, 1)
signal.Notify(sigChan, os.Interrupt, syscall.SIGTERM)
go func() {
<-sigChan
log.Debug().Msg("Command canceled with signal!")
cancel()
}()
log.Debug().Msgf("Running command> %s %s", opts.binary, strings.Join(opts.args, " "))
cmd := exec.Command(opts.binary, opts.args...)
var err error
if opts.background {
err = cmd.Start()
} else {
cmd.Stdin, cmd.Stdout, cmd.Stderr = os.Stdin, os.Stdout, os.Stderr
_, _ = cmd.Stdout.Write([]byte(opts.banner))
err = cmd.Run()
}
select {
case <-ctx.Done():
return errors.New("canceled by operator")
default:
return err
}
}
func clearScreen() {
fmt.Print("\033[H\033[2J")
}