mirror of
https://github.com/bcicen/ctop.git
synced 2024-08-30 18:23:19 +00:00
Merge branch 'exec' of https://github.com/fr05t1k/ctop into fr05t1k-exec
This commit is contained in:
commit
d7384db373
@ -70,6 +70,7 @@ Option | Description
|
|||||||
-s | select initial container sort field
|
-s | select initial container sort field
|
||||||
-scale-cpu | show cpu as % of system total
|
-scale-cpu | show cpu as % of system total
|
||||||
-v | output version information and exit
|
-v | output version information and exit
|
||||||
|
-shell | specify shell (default: sh)
|
||||||
|
|
||||||
### Keybindings
|
### Keybindings
|
||||||
|
|
||||||
@ -84,6 +85,7 @@ s | Select container sort field
|
|||||||
r | Reverse container sort order
|
r | Reverse container sort order
|
||||||
o | Open single view
|
o | Open single view
|
||||||
l | View container logs (`t` to toggle timestamp when open)
|
l | View container logs (`t` to toggle timestamp when open)
|
||||||
|
e | Exec Shell
|
||||||
S | Save current configuration to file
|
S | Save current configuration to file
|
||||||
q | Quit ctop
|
q | Quit ctop
|
||||||
|
|
||||||
|
@ -12,6 +12,11 @@ var params = []*Param{
|
|||||||
Val: "state",
|
Val: "state",
|
||||||
Label: "Container Sort Field",
|
Label: "Container Sort Field",
|
||||||
},
|
},
|
||||||
|
&Param{
|
||||||
|
Key: "shell",
|
||||||
|
Val: "sh",
|
||||||
|
Label: "Shell",
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
type Param struct {
|
type Param struct {
|
||||||
|
@ -3,6 +3,9 @@ package manager
|
|||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
api "github.com/fsouza/go-dockerclient"
|
api "github.com/fsouza/go-dockerclient"
|
||||||
|
"github.com/pkg/errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Docker struct {
|
type Docker struct {
|
||||||
@ -17,6 +20,85 @@ 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 {
|
||||||
|
wrappedReader io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (w *noClosableReader) Read(p []byte) (n int, err error) {
|
||||||
|
return w.wrappedReader.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) {
|
||||||
|
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 {
|
func (dc *Docker) Start() error {
|
||||||
c, err := dc.client.InspectContainer(dc.id)
|
c, err := dc.client.InspectContainer(dc.id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -7,4 +7,5 @@ type Manager interface {
|
|||||||
Pause() error
|
Pause() error
|
||||||
Unpause() error
|
Unpause() error
|
||||||
Restart() error
|
Restart() error
|
||||||
|
Exec(cmd []string) error
|
||||||
}
|
}
|
||||||
|
@ -29,3 +29,7 @@ func (m *Mock) Unpause() error {
|
|||||||
func (m *Mock) Restart() error {
|
func (m *Mock) Restart() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (m *Mock) Exec(cmd []string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -29,3 +29,7 @@ func (rc *Runc) Unpause() error {
|
|||||||
func (rc *Runc) Restart() error {
|
func (rc *Runc) Restart() error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (rc *Runc) Exec(cmd []string) error {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -153,3 +153,7 @@ func (c *Container) Restart() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Container) Exec(cmd []string) error {
|
||||||
|
return c.manager.Exec(cmd)
|
||||||
|
}
|
||||||
|
4
grid.go
4
grid.go
@ -116,6 +116,10 @@ func Display() bool {
|
|||||||
menu = LogMenu
|
menu = LogMenu
|
||||||
ui.StopLoop()
|
ui.StopLoop()
|
||||||
})
|
})
|
||||||
|
ui.Handle("/sys/kbd/e", func(ui.Event) {
|
||||||
|
menu = ExecShell
|
||||||
|
ui.StopLoop()
|
||||||
|
})
|
||||||
ui.Handle("/sys/kbd/o", func(ui.Event) {
|
ui.Handle("/sys/kbd/o", func(ui.Event) {
|
||||||
menu = SingleView
|
menu = SingleView
|
||||||
ui.StopLoop()
|
ui.StopLoop()
|
||||||
|
5
main.go
5
main.go
@ -45,6 +45,7 @@ func main() {
|
|||||||
invertFlag = flag.Bool("i", false, "invert default colors")
|
invertFlag = flag.Bool("i", false, "invert default colors")
|
||||||
scaleCpu = flag.Bool("scale-cpu", false, "show cpu as % of system total")
|
scaleCpu = flag.Bool("scale-cpu", false, "show cpu as % of system total")
|
||||||
connectorFlag = flag.String("connector", "docker", "container connector to use")
|
connectorFlag = flag.String("connector", "docker", "container connector to use")
|
||||||
|
defaultShell = flag.String("shell", "", "default shell")
|
||||||
)
|
)
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
|
|
||||||
@ -87,6 +88,10 @@ func main() {
|
|||||||
config.Toggle("scaleCpu")
|
config.Toggle("scaleCpu")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if *defaultShell != "" {
|
||||||
|
config.Update("shell", *defaultShell)
|
||||||
|
}
|
||||||
|
|
||||||
// init ui
|
// init ui
|
||||||
if *invertFlag {
|
if *invertFlag {
|
||||||
InvertColorMap()
|
InvertColorMap()
|
||||||
|
22
menus.go
22
menus.go
@ -25,6 +25,7 @@ var helpDialog = []menu.Item{
|
|||||||
{"[r] - reverse container sort order", ""},
|
{"[r] - reverse container sort order", ""},
|
||||||
{"[o] - open single view", ""},
|
{"[o] - open single view", ""},
|
||||||
{"[l] - view container logs ([t] to toggle timestamp when open)", ""},
|
{"[l] - view container logs ([t] to toggle timestamp when open)", ""},
|
||||||
|
{"[e] - exec shell", ""},
|
||||||
{"[S] - save current configuration to file", ""},
|
{"[S] - save current configuration to file", ""},
|
||||||
{"[q] - exit ctop", ""},
|
{"[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: "stop", Label: "[s] stop"})
|
||||||
items = append(items, menu.Item{Val: "pause", Label: "[p] pause"})
|
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: "restart", Label: "[r] restart"})
|
||||||
|
items = append(items, menu.Item{Val: "exec shell", Label: "[e]xec shell"})
|
||||||
}
|
}
|
||||||
if c.Meta["state"] == "exited" || c.Meta["state"] == "created" {
|
if c.Meta["state"] == "exited" || c.Meta["state"] == "created" {
|
||||||
items = append(items, menu.Item{Val: "start", Label: "[s] start"})
|
items = append(items, menu.Item{Val: "start", Label: "[s] start"})
|
||||||
@ -210,6 +212,8 @@ func ContainerMenu() MenuFn {
|
|||||||
nextMenu = SingleView
|
nextMenu = SingleView
|
||||||
case "logs":
|
case "logs":
|
||||||
nextMenu = LogMenu
|
nextMenu = LogMenu
|
||||||
|
case "exec shell":
|
||||||
|
nextMenu = ExecShell
|
||||||
case "start":
|
case "start":
|
||||||
nextMenu = Confirm(confirmTxt("start", c.GetMeta("name")), c.Start)
|
nextMenu = Confirm(confirmTxt("start", c.GetMeta("name")), c.Start)
|
||||||
case "stop":
|
case "stop":
|
||||||
@ -256,6 +260,24 @@ func LogMenu() MenuFn {
|
|||||||
return nil
|
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
|
// Create a confirmation dialog with a given description string and
|
||||||
// func to perform if confirmed
|
// func to perform if confirmed
|
||||||
func Confirm(txt string, fn func()) MenuFn {
|
func Confirm(txt string, fn func()) MenuFn {
|
||||||
|
Loading…
Reference in New Issue
Block a user