Merge branch 'fr05t1k-exec'

This commit is contained in:
Bradley Cicenas 2019-05-12 16:26:51 -04:00
commit c1d4615cc0
10 changed files with 140 additions and 0 deletions

View File

@ -70,6 +70,7 @@ Option | Description
-s | select initial container sort field
-scale-cpu | show cpu as % of system total
-v | output version information and exit
-shell | specify shell (default: sh)
### Keybindings
@ -84,6 +85,7 @@ s | Select container sort field
r | Reverse container sort order
o | Open single view
l | View container logs (`t` to toggle timestamp when open)
e | Exec Shell
S | Save current configuration to file
q | Quit ctop

View File

@ -12,6 +12,11 @@ var params = []*Param{
Val: "state",
Label: "Container Sort Field",
},
&Param{
Key: "shell",
Val: "sh",
Label: "Shell",
},
}
type Param struct {

View File

@ -3,6 +3,9 @@ package manager
import (
"fmt"
api "github.com/fsouza/go-dockerclient"
"github.com/pkg/errors"
"io"
"os"
)
type Docker struct {
@ -17,6 +20,88 @@ func NewDocker(client *api.Client, id string) *Docker {
}
}
// Do not allow to close reader (i.e. /dev/stdin which docker client tries to close after command execution)
type noClosableReader struct {
io.Reader
}
func (w *noClosableReader) Read(p []byte) (n int, err error) {
return w.Reader.Read(p)
}
const (
STDIN = 0
STDOUT = 1
STDERR = 2
)
var wrongFrameFormat = errors.New("Wrong frame format")
// A frame has a Header and a Payload
// Header: [8]byte{STREAM_TYPE, 0, 0, 0, SIZE1, SIZE2, SIZE3, SIZE4}
// STREAM_TYPE can be:
// 0: stdin (is written on stdout)
// 1: stdout
// 2: stderr
// SIZE1, SIZE2, SIZE3, SIZE4 are the four bytes of the uint32 size encoded as big endian.
// But we don't use size, because we don't need to find the end of frame.
type frameWriter struct {
stdout io.Writer
stderr io.Writer
stdin io.Writer
}
func (w *frameWriter) Write(p []byte) (n int, err error) {
// drop initial empty frames
if len(p) == 0 {
return 0, nil
}
if len(p) > 8 {
var targetWriter io.Writer
switch p[0] {
case STDIN:
targetWriter = w.stdin
break
case STDOUT:
targetWriter = w.stdout
break
case STDERR:
targetWriter = w.stderr
break
default:
return 0, wrongFrameFormat
}
n, err := targetWriter.Write(p[8:])
return n + 8, err
}
return 0, wrongFrameFormat
}
func (dc *Docker) Exec(cmd []string) error {
execCmd, err := dc.client.CreateExec(api.CreateExecOptions{
AttachStdin: true,
AttachStdout: true,
AttachStderr: true,
Cmd: cmd,
Container: dc.id,
Tty: true,
})
if err != nil {
return err
}
return dc.client.StartExec(execCmd.ID, api.StartExecOptions{
InputStream: &noClosableReader{os.Stdin},
OutputStream: &frameWriter{os.Stdout, os.Stderr, os.Stdin},
ErrorStream: os.Stderr,
RawTerminal: true,
})
}
func (dc *Docker) Start() error {
c, err := dc.client.InspectContainer(dc.id)
if err != nil {

View File

@ -7,4 +7,5 @@ type Manager interface {
Pause() error
Unpause() error
Restart() error
Exec(cmd []string) error
}

View File

@ -29,3 +29,7 @@ func (m *Mock) Unpause() error {
func (m *Mock) Restart() error {
return nil
}
func (m *Mock) Exec(cmd []string) error {
return nil
}

View File

@ -29,3 +29,7 @@ func (rc *Runc) Unpause() error {
func (rc *Runc) Restart() error {
return nil
}
func (rc *Runc) Exec(cmd []string) error {
return nil
}

View File

@ -153,3 +153,7 @@ func (c *Container) Restart() {
}
}
}
func (c *Container) Exec(cmd []string) error {
return c.manager.Exec(cmd)
}

View File

@ -116,6 +116,10 @@ func Display() bool {
menu = LogMenu
ui.StopLoop()
})
ui.Handle("/sys/kbd/e", func(ui.Event) {
menu = ExecShell
ui.StopLoop()
})
ui.Handle("/sys/kbd/o", func(ui.Event) {
menu = SingleView
ui.StopLoop()

View File

@ -45,6 +45,7 @@ func main() {
invertFlag = flag.Bool("i", false, "invert default colors")
scaleCpu = flag.Bool("scale-cpu", false, "show cpu as % of system total")
connectorFlag = flag.String("connector", "docker", "container connector to use")
defaultShell = flag.String("shell", "", "default shell")
)
flag.Parse()
@ -87,6 +88,10 @@ func main() {
config.Toggle("scaleCpu")
}
if *defaultShell != "" {
config.Update("shell", *defaultShell)
}
// init ui
if *invertFlag {
InvertColorMap()

View File

@ -25,6 +25,7 @@ var helpDialog = []menu.Item{
{"[r] - reverse container sort order", ""},
{"[o] - open single view", ""},
{"[l] - view container logs ([t] to toggle timestamp when open)", ""},
{"[e] - exec shell", ""},
{"[S] - save current configuration to file", ""},
{"[q] - exit ctop", ""},
}
@ -134,6 +135,7 @@ func ContainerMenu() MenuFn {
items = append(items, menu.Item{Val: "stop", Label: "[s] stop"})
items = append(items, menu.Item{Val: "pause", Label: "[p] pause"})
items = append(items, menu.Item{Val: "restart", Label: "[r] restart"})
items = append(items, menu.Item{Val: "exec", Label: "[e] exec shell"})
}
if c.Meta["state"] == "exited" || c.Meta["state"] == "created" {
items = append(items, menu.Item{Val: "start", Label: "[s] start"})
@ -182,6 +184,10 @@ func ContainerMenu() MenuFn {
})
}
if c.Meta["state"] == "running" {
ui.Handle("/sys/kbd/e", func(ui.Event) {
selected = "exec"
ui.StopLoop()
})
ui.Handle("/sys/kbd/r", func(ui.Event) {
selected = "restart"
ui.StopLoop()
@ -210,6 +216,8 @@ func ContainerMenu() MenuFn {
nextMenu = SingleView
case "logs":
nextMenu = LogMenu
case "exec":
nextMenu = ExecShell
case "start":
nextMenu = Confirm(confirmTxt("start", c.GetMeta("name")), c.Start)
case "stop":
@ -256,6 +264,24 @@ func LogMenu() MenuFn {
return nil
}
func ExecShell() MenuFn {
c := cursor.Selected()
if c == nil {
return nil
}
ui.DefaultEvtStream.ResetHandlers()
defer ui.DefaultEvtStream.ResetHandlers()
shell := config.Get("shell")
if err := c.Exec([]string{shell.Val, "-c", "echo '\033[0m' && clear && " + shell.Val}); err != nil {
log.Fatal(err)
}
return nil
}
// Create a confirmation dialog with a given description string and
// func to perform if confirmed
func Confirm(txt string, fn func()) MenuFn {