From 18f6c065e1c553c388a016b22a205dbde8882d6b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Fri, 21 Sep 2018 14:59:45 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=E6=94=AF=E6=8C=81ssh=20webconsole?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/shell/webconsole.go | 9 +++ pkg/mcclient/modules/mod_webconsole.go | 4 ++ pkg/mcclient/options/webconsole.go | 5 ++ pkg/webconsole/command/ssh_command.go | 84 ++++++++++++++++++++++++++ pkg/webconsole/handlers.go | 29 ++++++++- pkg/webconsole/options/options.go | 8 ++- 6 files changed, 135 insertions(+), 4 deletions(-) create mode 100644 pkg/webconsole/command/ssh_command.go diff --git a/cmd/climc/shell/webconsole.go b/cmd/climc/shell/webconsole.go index 0a784dc555..7658e8e158 100644 --- a/cmd/climc/shell/webconsole.go +++ b/cmd/climc/shell/webconsole.go @@ -65,4 +65,13 @@ func init() { handleResult(args.WebConsoleOptions, ret) return nil }) + + R(&o.WebConsoleServerOptions{}, "webconsole-server", "Connect server webconsole", func(s *mcclient.ClientSession, args *o.WebConsoleServerOptions) error { + ret, err := modules.WebConsole.DoServerConnect(s, args.ID) + if err != nil { + return err + } + handleResult(args.WebConsoleOptions, ret) + return nil + }) } diff --git a/pkg/mcclient/modules/mod_webconsole.go b/pkg/mcclient/modules/mod_webconsole.go index 0c1b79f985..ad0509a21b 100644 --- a/pkg/mcclient/modules/mod_webconsole.go +++ b/pkg/mcclient/modules/mod_webconsole.go @@ -69,3 +69,7 @@ func (m WebConsoleManager) DoK8sLogConnect( func (m WebConsoleManager) DoBaremetalConnect(s *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) { return m.DoConnect(s, "baremetal", id, "", nil) } + +func (m WebConsoleManager) DoServerConnect(s *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) { + return m.DoConnect(s, "server", id, "", nil) +} diff --git a/pkg/mcclient/options/webconsole.go b/pkg/mcclient/options/webconsole.go index 723c03da6f..d01e521985 100644 --- a/pkg/mcclient/options/webconsole.go +++ b/pkg/mcclient/options/webconsole.go @@ -36,3 +36,8 @@ type WebConsoleBaremetalOptions struct { func (opt *WebConsoleBaremetalOptions) Params() (*jsonutils.JSONDict, error) { return StructToParams(opt) } + +type WebConsoleServerOptions struct { + WebConsoleOptions + ID string `help:"Server id or name"` +} diff --git a/pkg/webconsole/command/ssh_command.go b/pkg/webconsole/command/ssh_command.go new file mode 100644 index 0000000000..1d6423e454 --- /dev/null +++ b/pkg/webconsole/command/ssh_command.go @@ -0,0 +1,84 @@ +package command + +import ( + "fmt" + "net" + "os/exec" + "strings" + + o "yunion.io/x/onecloud/pkg/webconsole/options" + "yunion.io/x/pkg/utils" +) + +type Metadata struct { + LoginAccount string `json:"login_account"` + LoginKey string `json:"login_key"` +} + +type SSHInfo struct { + ID string `json:"id"` + Metadata Metadata `json:"metadata"` + Eip string `json:"eip"` + IPs string `json:"ips"` + Keypaire string `json:"keypair"` + OsType string `json:"os_type"` +} + +type SSHtoolSol struct { + *BaseCommand + Info *SSHInfo +} + +func NewSSHtoolSolCommand(info *SSHInfo) (*SSHtoolSol, error) { + if info.IPs == "" { + return nil, fmt.Errorf("Empty server ip address") + } + if info.Metadata.LoginAccount == "" { + return nil, fmt.Errorf("Empty username") + } + if len(info.Keypaire) != 0 { + return nil, fmt.Errorf("Not support private_key login") + } + if info.OsType != "Linux" { + return nil, fmt.Errorf("Not support login for %s", info.OsType) + } + args := "" + if info.Eip != "" { + args = fmt.Sprintf("%s@%s", info.Metadata.LoginAccount, info.Eip) + } else { + for _, ip := range strings.Split(info.IPs, ",") { + conn, err := net.Dial("tcp", fmt.Sprintf("%s:22", ip)) + if err == nil { + args = fmt.Sprintf("%s@%s", info.Metadata.LoginAccount, ip) + break + } + defer conn.Close() + } + } + if len(args) == 0 { + return nil, fmt.Errorf("failed find usable connection ip address") + } + cmd := NewBaseCommand(o.Options.SSHtoolPath) + if info.Metadata.LoginKey != "" { + cmd := NewBaseCommand(o.Options.SSHtoolPath) + if passwd, err := utils.DescryptAESBase64(info.ID, info.Metadata.LoginKey); err != nil { + return nil, err + } else { + cmd.AppendArgs("-p", passwd) + } + } + cmd.AppendArgs(args) + tool := &SSHtoolSol{ + BaseCommand: cmd, + Info: info, + } + return tool, nil +} + +func (c *SSHtoolSol) GetCommand() *exec.Cmd { + return c.BaseCommand.GetCommand() +} + +func (c SSHtoolSol) GetProtocol() string { + return PROTOCOL_TTY +} diff --git a/pkg/webconsole/handlers.go b/pkg/webconsole/handlers.go index cd44f4f0c0..18eb95c89b 100644 --- a/pkg/webconsole/handlers.go +++ b/pkg/webconsole/handlers.go @@ -31,6 +31,7 @@ func InitHandlers(app *appsrv.Application) { app.AddHandler("POST", ApiPathPrefix+"k8s//shell", auth.Authenticate(handleK8sShell)) app.AddHandler("POST", ApiPathPrefix+"k8s//log", auth.Authenticate(handleK8sLog)) app.AddHandler("POST", ApiPathPrefix+"baremetal/", auth.Authenticate(handleBaremetalShell)) + app.AddHandler("POST", ApiPathPrefix+"server/", auth.Authenticate(handleServerShell)) } func fetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) { @@ -122,7 +123,7 @@ func fetchCloudEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) if userCred == nil { return nil, httperrors.NewUnauthorizedError("No token founded") } - s := auth.Client().NewSession(o.Options.Region, "", "internal", userCred, "") + s := auth.Client().NewSession(o.Options.Region, "", "internal", userCred, "v2") return &CloudEnv{ ClientSessin: s, Params: params, @@ -156,6 +157,32 @@ func handleK8sLog(ctx context.Context, w http.ResponseWriter, r *http.Request) { handleK8sCommand(ctx, w, r, command.NewPodLogCommand) } +func handleServerShell(ctx context.Context, w http.ResponseWriter, r *http.Request) { + env, err := fetchCloudEnv(ctx, w, r) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + serverId := env.Params[""] + ret, err := modules.Servers.Get(env.ClientSessin, serverId, jsonutils.Marshal(map[string]bool{"with_meta": true})) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + info := command.SSHInfo{} + err = ret.Unmarshal(&info) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + cmd, err := command.NewSSHtoolSolCommand(&info) + if err != nil { + httperrors.GeneralServerError(w, err) + return + } + handleCommandSession(cmd, w) +} + func handleBaremetalShell(ctx context.Context, w http.ResponseWriter, r *http.Request) { env, err := fetchCloudEnv(ctx, w, r) if err != nil { diff --git a/pkg/webconsole/options/options.go b/pkg/webconsole/options/options.go index 45e9235f00..b504dfa7af 100644 --- a/pkg/webconsole/options/options.go +++ b/pkg/webconsole/options/options.go @@ -11,7 +11,9 @@ var ( type WebConsoleOptions struct { cloudcommon.Options - ApiServer string `help:"API server url to handle websocket connection, usually with public access" default:"http://webconsole.yunion.io"` - KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"` - IpmitoolPath string `help:"ipmitool binary path used to connect baremetal sol" default:"/usr/bin/ipmitool"` + ApiServer string `help:"API server url to handle websocket connection, usually with public access" default:"http://webconsole.yunion.io"` + KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"` + IpmitoolPath string `help:"ipmitool binary path used to connect baremetal sol" default:"/usr/bin/ipmitool"` + SSHtoolPath string `help:"sshtool binary path used to connect server sol" default:"/usr/bin/ssh"` + SSHPasstoolPath string `help:"sshpass tool binary path used to connect server sol" default:"/usr/local/bin/sshpass"` } From 9673892de03c4e66b4917bcd2564cdb8f8f59281 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Fri, 21 Sep 2018 15:12:59 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0admin=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pkg/webconsole/handlers.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/webconsole/handlers.go b/pkg/webconsole/handlers.go index 18eb95c89b..b470820f57 100644 --- a/pkg/webconsole/handlers.go +++ b/pkg/webconsole/handlers.go @@ -164,7 +164,7 @@ func handleServerShell(ctx context.Context, w http.ResponseWriter, r *http.Reque return } serverId := env.Params[""] - ret, err := modules.Servers.Get(env.ClientSessin, serverId, jsonutils.Marshal(map[string]bool{"with_meta": true})) + ret, err := modules.Servers.Get(env.ClientSessin, serverId, jsonutils.Marshal(map[string]bool{"with_meta": true, "admin": true})) if err != nil { httperrors.GeneralServerError(w, err) return From 28d59c5ebd6258a80b98e41b58038d098220d31a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B1=88=E8=BD=A9?= Date: Fri, 21 Sep 2018 15:19:11 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=E8=B6=85=E6=97=B6?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- cmd/climc/shell/webconsole.go | 4 +- pkg/mcclient/modules/mod_webconsole.go | 4 +- pkg/mcclient/options/webconsole.go | 4 +- pkg/webconsole/command/command.go | 10 +++ pkg/webconsole/command/ssh_command.go | 110 +++++++++++-------------- pkg/webconsole/handlers.go | 18 +--- pkg/webconsole/options/options.go | 4 +- pkg/webconsole/server/tty_server.go | 66 +++++++++++++-- pkg/webconsole/service/service.go | 2 +- pkg/webconsole/session/pty_session.go | 49 +++++++---- 10 files changed, 162 insertions(+), 109 deletions(-) diff --git a/cmd/climc/shell/webconsole.go b/cmd/climc/shell/webconsole.go index 7658e8e158..e8d4a26d9f 100644 --- a/cmd/climc/shell/webconsole.go +++ b/cmd/climc/shell/webconsole.go @@ -66,8 +66,8 @@ func init() { return nil }) - R(&o.WebConsoleServerOptions{}, "webconsole-server", "Connect server webconsole", func(s *mcclient.ClientSession, args *o.WebConsoleServerOptions) error { - ret, err := modules.WebConsole.DoServerConnect(s, args.ID) + R(&o.WebConsoleSshOptions{}, "webconsole-ssh", "Connect ssh webconsole", func(s *mcclient.ClientSession, args *o.WebConsoleSshOptions) error { + ret, err := modules.WebConsole.DoSshConnect(s, args.IP) if err != nil { return err } diff --git a/pkg/mcclient/modules/mod_webconsole.go b/pkg/mcclient/modules/mod_webconsole.go index ad0509a21b..963240028d 100644 --- a/pkg/mcclient/modules/mod_webconsole.go +++ b/pkg/mcclient/modules/mod_webconsole.go @@ -70,6 +70,6 @@ func (m WebConsoleManager) DoBaremetalConnect(s *mcclient.ClientSession, id stri return m.DoConnect(s, "baremetal", id, "", nil) } -func (m WebConsoleManager) DoServerConnect(s *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) { - return m.DoConnect(s, "server", id, "", nil) +func (m WebConsoleManager) DoSshConnect(s *mcclient.ClientSession, id string) (jsonutils.JSONObject, error) { + return m.DoConnect(s, "ssh", id, "", nil) } diff --git a/pkg/mcclient/options/webconsole.go b/pkg/mcclient/options/webconsole.go index d01e521985..ba9a36db14 100644 --- a/pkg/mcclient/options/webconsole.go +++ b/pkg/mcclient/options/webconsole.go @@ -37,7 +37,7 @@ func (opt *WebConsoleBaremetalOptions) Params() (*jsonutils.JSONDict, error) { return StructToParams(opt) } -type WebConsoleServerOptions struct { +type WebConsoleSshOptions struct { WebConsoleOptions - ID string `help:"Server id or name"` + IP string `help:"IP to connect"` } diff --git a/pkg/webconsole/command/command.go b/pkg/webconsole/command/command.go index 35ed12f53d..faccd18e88 100644 --- a/pkg/webconsole/command/command.go +++ b/pkg/webconsole/command/command.go @@ -15,6 +15,8 @@ type ICommand interface { GetProtocol() string GetCommand() *exec.Cmd Cleanup() error + GetData(string) (isShow bool, ouput string, command string) + ShowInfo() string } type BaseCommand struct { @@ -40,6 +42,14 @@ func (c BaseCommand) GetCommand() *exec.Cmd { return exec.Command(c.name, c.args...) } +func (c BaseCommand) GetData(comand string) (isShow bool, ouput string, command string) { + return true, "", "" +} + +func (c BaseCommand) ShowInfo() string { + return "" +} + func (c BaseCommand) Cleanup() error { log.Infof("BaseCommand Cleanup do nothing") return nil diff --git a/pkg/webconsole/command/ssh_command.go b/pkg/webconsole/command/ssh_command.go index 1d6423e454..8bd6cce9c1 100644 --- a/pkg/webconsole/command/ssh_command.go +++ b/pkg/webconsole/command/ssh_command.go @@ -4,81 +4,69 @@ import ( "fmt" "net" "os/exec" - "strings" + "time" + "yunion.io/x/log" o "yunion.io/x/onecloud/pkg/webconsole/options" - "yunion.io/x/pkg/utils" ) -type Metadata struct { - LoginAccount string `json:"login_account"` - LoginKey string `json:"login_key"` -} - -type SSHInfo struct { - ID string `json:"id"` - Metadata Metadata `json:"metadata"` - Eip string `json:"eip"` - IPs string `json:"ips"` - Keypaire string `json:"keypair"` - OsType string `json:"os_type"` -} - type SSHtoolSol struct { *BaseCommand - Info *SSHInfo + IP string + Username string + reTry int + showInfo string } -func NewSSHtoolSolCommand(info *SSHInfo) (*SSHtoolSol, error) { - if info.IPs == "" { - return nil, fmt.Errorf("Empty server ip address") - } - if info.Metadata.LoginAccount == "" { - return nil, fmt.Errorf("Empty username") - } - if len(info.Keypaire) != 0 { - return nil, fmt.Errorf("Not support private_key login") - } - if info.OsType != "Linux" { - return nil, fmt.Errorf("Not support login for %s", info.OsType) - } - args := "" - if info.Eip != "" { - args = fmt.Sprintf("%s@%s", info.Metadata.LoginAccount, info.Eip) +func NewSSHtoolSolCommand(ip string) (*SSHtoolSol, error) { + if conn, err := net.DialTimeout("tcp", ip+":22", time.Second*2); err != nil { + return nil, fmt.Errorf("IPAddress %s not accessable", ip) } else { - for _, ip := range strings.Split(info.IPs, ",") { - conn, err := net.Dial("tcp", fmt.Sprintf("%s:22", ip)) - if err == nil { - args = fmt.Sprintf("%s@%s", info.Metadata.LoginAccount, ip) - break - } - defer conn.Close() - } + conn.Close() + return &SSHtoolSol{ + BaseCommand: nil, + IP: ip, + Username: "", + reTry: 0, + showInfo: fmt.Sprintf("%s login:", ip), + }, nil } - if len(args) == 0 { - return nil, fmt.Errorf("failed find usable connection ip address") - } - cmd := NewBaseCommand(o.Options.SSHtoolPath) - if info.Metadata.LoginKey != "" { - cmd := NewBaseCommand(o.Options.SSHtoolPath) - if passwd, err := utils.DescryptAESBase64(info.ID, info.Metadata.LoginKey); err != nil { - return nil, err - } else { - cmd.AppendArgs("-p", passwd) - } - } - cmd.AppendArgs(args) - tool := &SSHtoolSol{ - BaseCommand: cmd, - Info: info, - } - return tool, nil } func (c *SSHtoolSol) GetCommand() *exec.Cmd { - return c.BaseCommand.GetCommand() + return nil } -func (c SSHtoolSol) GetProtocol() string { +func (c *SSHtoolSol) Cleanup() error { + log.Infof("SSHtoolSol Cleanup do nothing") + return nil +} + +func (c *SSHtoolSol) GetProtocol() string { return PROTOCOL_TTY } + +func (c *SSHtoolSol) GetData(data string) (isShow bool, ouput string, command string) { + if len(c.Username) == 0 { + if len(data) == 0 { + //用户名不能为空 + return true, c.showInfo, "" + } + c.Username = data + return false, "Password:", "" + } else { + return true, "", fmt.Sprintf("%s -p %s %s %s@%s", o.Options.SshpassToolPath, data, o.Options.SshToolPath, c.Username, c.IP) + } +} + +func (c *SSHtoolSol) ShowInfo() string { + c.Username = "" + c.reTry++ + if c.reTry == 3 { + c.reTry = 0 + //清屏 + time.Sleep(1 * time.Second) + return "\033c " + c.showInfo + } + return c.showInfo +} diff --git a/pkg/webconsole/handlers.go b/pkg/webconsole/handlers.go index b470820f57..39f5627924 100644 --- a/pkg/webconsole/handlers.go +++ b/pkg/webconsole/handlers.go @@ -31,7 +31,7 @@ func InitHandlers(app *appsrv.Application) { app.AddHandler("POST", ApiPathPrefix+"k8s//shell", auth.Authenticate(handleK8sShell)) app.AddHandler("POST", ApiPathPrefix+"k8s//log", auth.Authenticate(handleK8sLog)) app.AddHandler("POST", ApiPathPrefix+"baremetal/", auth.Authenticate(handleBaremetalShell)) - app.AddHandler("POST", ApiPathPrefix+"server/", auth.Authenticate(handleServerShell)) + app.AddHandler("POST", ApiPathPrefix+"ssh/", auth.Authenticate(handleSshShell)) } func fetchEnv(ctx context.Context, w http.ResponseWriter, r *http.Request) (map[string]string, jsonutils.JSONObject, jsonutils.JSONObject) { @@ -157,25 +157,13 @@ func handleK8sLog(ctx context.Context, w http.ResponseWriter, r *http.Request) { handleK8sCommand(ctx, w, r, command.NewPodLogCommand) } -func handleServerShell(ctx context.Context, w http.ResponseWriter, r *http.Request) { +func handleSshShell(ctx context.Context, w http.ResponseWriter, r *http.Request) { env, err := fetchCloudEnv(ctx, w, r) if err != nil { httperrors.GeneralServerError(w, err) return } - serverId := env.Params[""] - ret, err := modules.Servers.Get(env.ClientSessin, serverId, jsonutils.Marshal(map[string]bool{"with_meta": true, "admin": true})) - if err != nil { - httperrors.GeneralServerError(w, err) - return - } - info := command.SSHInfo{} - err = ret.Unmarshal(&info) - if err != nil { - httperrors.GeneralServerError(w, err) - return - } - cmd, err := command.NewSSHtoolSolCommand(&info) + cmd, err := command.NewSSHtoolSolCommand(env.Params[""]) if err != nil { httperrors.GeneralServerError(w, err) return diff --git a/pkg/webconsole/options/options.go b/pkg/webconsole/options/options.go index b504dfa7af..d881ea9386 100644 --- a/pkg/webconsole/options/options.go +++ b/pkg/webconsole/options/options.go @@ -14,6 +14,6 @@ type WebConsoleOptions struct { ApiServer string `help:"API server url to handle websocket connection, usually with public access" default:"http://webconsole.yunion.io"` KubectlPath string `help:"kubectl binary path used to connect k8s cluster" default:"/usr/bin/kubectl"` IpmitoolPath string `help:"ipmitool binary path used to connect baremetal sol" default:"/usr/bin/ipmitool"` - SSHtoolPath string `help:"sshtool binary path used to connect server sol" default:"/usr/bin/ssh"` - SSHPasstoolPath string `help:"sshpass tool binary path used to connect server sol" default:"/usr/local/bin/sshpass"` + SshToolPath string `help:"sshtool binary path used to connect server sol" default:"/usr/bin/ssh"` + SshpassToolPath string `help:"sshpass tool binary path used to connect server sol" default:"/usr/local/bin/sshpass"` } diff --git a/pkg/webconsole/server/tty_server.go b/pkg/webconsole/server/tty_server.go index 717f7d1fa4..4cf4f0757d 100644 --- a/pkg/webconsole/server/tty_server.go +++ b/pkg/webconsole/server/tty_server.go @@ -1,6 +1,11 @@ package server import ( + "os/exec" + "strconv" + "strings" + "time" + socketio "github.com/googollee/go-socket.io" "github.com/kr/pty" @@ -56,19 +61,67 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) { go func() { buf := make([]byte, 1024) for { - n, err := p.Pty.Read(buf) - if err != nil { - log.Errorf("Failed to read from pty master: %v", err) - cleanUp(so, p) + if p.IsOk { + if p.Cmd == nil || p.Cmd.Process == nil { + p.IsOk = false + } else if p.Pty == nil { + p.IsOk = false + } else if n, err := p.Pty.Read(buf); err != nil { + p.IsOk = false + } else { + so.Emit(OUTPUT_EVENT, string(buf[0:n])) + } + if !p.IsOk { + p.Stop() + if info := p.Session.ShowInfo(); len(info) > 0 { + so.Emit(OUTPUT_EVENT, info) + } + } + } else if p.Exit { return + } else { + //避免goroutine死循环导致主进程卡死 + time.Sleep(time.Microsecond * 50) } - so.Emit(OUTPUT_EVENT, string(buf[0:n])) } }() // handle write so.On(INPUT_EVENT, func(data string) { - p.Pty.Write([]byte(data)) + if !p.IsOk { + if data == "\r" { + p.Show, p.Output, p.Command = p.Session.GetData(p.Buffer) + so.Emit(OUTPUT_EVENT, "\r\n") + if len(p.Output) > 0 { + so.Emit(OUTPUT_EVENT, p.Output) + } + if len(p.Command) > 0 { + log.Infof("exec: %s", p.Command) + args := strings.Split(p.Command, " ") + cmd := exec.Command(args[0], args[1:]...) + if _pty, err := pty.Start(cmd); err != nil { + so.Emit(OUTPUT_EVENT, err.Error()+"\r\n") + log.Errorf("exec error: %v", err) + } else { + p.Pty, p.Cmd, p.IsOk = _pty, cmd, true + } + } + p.Buffer, data = "", "" + } else if data == "\u007f" { + //退格处理 + if len(p.Buffer) > 0 { + p.Buffer = p.Buffer[:len(p.Buffer)-1] + data = "\b \b" + } + } else if strconv.IsPrint([]rune(data)[0]) { + p.Buffer += data + } + if p.Show && len(data) > 0 { + so.Emit(OUTPUT_EVENT, data) + } + } else { + p.Pty.Write([]byte(data)) + } }) // handle resize @@ -106,4 +159,5 @@ func initSocketHandler(so socketio.Socket, p *session.Pty) { func cleanUp(so socketio.Socket, p *session.Pty) { so.Disconnect() p.Stop() + p.Exit = true } diff --git a/pkg/webconsole/service/service.go b/pkg/webconsole/service/service.go index 3836f618bb..3cf4f09adb 100644 --- a/pkg/webconsole/service/service.go +++ b/pkg/webconsole/service/service.go @@ -34,7 +34,7 @@ func StartService() { log.Fatalf("invalid --api-server %s", o.Options.ApiServer) } - for _, binPath := range []string{o.Options.KubectlPath, o.Options.IpmitoolPath} { + for _, binPath := range []string{o.Options.KubectlPath, o.Options.IpmitoolPath, o.Options.SshToolPath, o.Options.SshpassToolPath} { ensureBinExists(binPath) } diff --git a/pkg/webconsole/session/pty_session.go b/pkg/webconsole/session/pty_session.go index c28d0e6542..7cb2ea8182 100644 --- a/pkg/webconsole/session/pty_session.go +++ b/pkg/webconsole/session/pty_session.go @@ -17,6 +17,12 @@ type Pty struct { Pty *os.File sizeCh chan os.Signal size *pty.Winsize + Show bool + IsOk bool + Buffer string + Output string + Command string + Exit bool } func NewPty(session *SSession) (p *Pty, err error) { @@ -24,11 +30,17 @@ func NewPty(session *SSession) (p *Pty, err error) { p = &Pty{ Session: session, Cmd: cmd, + Show: true, + IsOk: true, + Exit: false, + Pty: nil, } log.Debugf("[session %s] Start command: %#v", session.Id, cmd) - p.Pty, err = pty.Start(p.Cmd) - if err != nil { - return + if cmd != nil { + p.Pty, err = pty.Start(p.Cmd) + if err != nil { + return + } } p.sizeCh = make(chan os.Signal, 1) p.size = &pty.Winsize{} @@ -40,10 +52,12 @@ func NewPty(session *SSession) (p *Pty, err error) { func (p *Pty) startResizeMonitor() { go func() { for range p.sizeCh { - if err := pty.Setsize(p.Pty, p.size); err != nil { - log.Errorf("Resize pty error: %v", err) - } else { - log.Debugf("Resize pty to %#v, cmd: %#v", p.size, p.Cmd) + if p.Pty != nil { + if err := pty.Setsize(p.Pty, p.size); err != nil { + log.Errorf("Resize pty error: %v", err) + } else { + log.Debugf("Resize pty to %#v, cmd: %#v", p.size, p.Cmd) + } } } }() @@ -55,18 +69,17 @@ func (p *Pty) Resize(size *pty.Winsize) { } func (p *Pty) Stop() { - var err error - err = p.Pty.Close() - if err != nil { - log.Errorf("Close PTY error: %v", err) + if p.Pty != nil { + if err := p.Pty.Close(); err != nil { + log.Errorf("Close PTY error: %v", err) + } } - err = p.Cmd.Process.Signal(os.Kill) - if err != nil { - log.Errorf("Kill command process error: %v", err) - } - err = p.Cmd.Wait() - if err != nil { - log.Errorf("Wait command error: %v", err) + if p.Cmd != nil && p.Cmd.Process != nil { + if err := p.Cmd.Process.Signal(os.Kill); err != nil { + log.Errorf("Kill command process error: %v", err) + } else if err := p.Cmd.Wait(); err != nil { + log.Errorf("Wait command error: %v", err) + } } p.Session.Close() }