diff options
author | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2018-02-23 18:45:15 +0100 |
---|---|---|
committer | Sebastien Douheret <sebastien.douheret@iot.bzh> | 2018-02-23 18:45:15 +0100 |
commit | 65e09e831cf13343ac713fbf15281174d1f13a94 (patch) | |
tree | 0176a263ecf6d1059a272007c6f450770f5792ca /lib/xdsserver/terminal-ssh.go | |
parent | e97eebc18d726aa55738d7e19513491cf58a6e3a (diff) |
Added target and terminal support.v1.1.0
Signed-off-by: Sebastien Douheret <sebastien.douheret@iot.bzh>
Diffstat (limited to 'lib/xdsserver/terminal-ssh.go')
-rw-r--r-- | lib/xdsserver/terminal-ssh.go | 265 |
1 files changed, 265 insertions, 0 deletions
diff --git a/lib/xdsserver/terminal-ssh.go b/lib/xdsserver/terminal-ssh.go new file mode 100644 index 0000000..3f4a344 --- /dev/null +++ b/lib/xdsserver/terminal-ssh.go @@ -0,0 +1,265 @@ +/* + * Copyright (C) 2018 "IoT.bzh" + * Author Sebastien Douheret <sebastien@iot.bzh> + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package xdsserver + +import ( + "fmt" + "strings" + "time" + + "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib/eows" + "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1" + socketio "github.com/googollee/go-socket.io" + uuid "github.com/satori/go.uuid" +) + +// ITARGET interface implementation for standard targets + +// TermSSH . +type TermSSH struct { + *Context + termCfg xsapiv1.TerminalConfig + targetID string + sshWS *eows.ExecOverWS +} + +// NewTermSSH Create a new instance of TermSSH +func NewTermSSH(ctx *Context, cfg xsapiv1.TerminalConfig, targetID string) *TermSSH { + + // Allocate and set default settings + t := TermSSH{ + Context: ctx, + termCfg: xsapiv1.TerminalConfig{ + ID: cfg.ID, + Name: "ssh", + Type: xsapiv1.TypeTermSSH, + Status: xsapiv1.StatusTermClose, + User: "", + Options: []string{""}, + Cols: 80, + Rows: 24, + }, + targetID: targetID, + } + + t.UpdateConfig(cfg) + return &t +} + +// NewUID Get a UUID +func (t *TermSSH) _NewUID(suffix string) string { + uuid := uuid.NewV1().String() + if len(suffix) > 0 { + uuid += "_" + suffix + } + return uuid +} + +// GetConfig Get public part of terminal config +func (t *TermSSH) GetConfig() xsapiv1.TerminalConfig { + return t.termCfg +} + +// UpdateConfig Update terminal config +func (t *TermSSH) UpdateConfig(newCfg xsapiv1.TerminalConfig) *xsapiv1.TerminalConfig { + + if t.termCfg.ID == "" { + if newCfg.ID != "" { + t.termCfg.ID = newCfg.ID + } else { + t.termCfg.ID = t._NewUID("") + } + } + if newCfg.Name != "" { + t.termCfg.Name = newCfg.Name + } + if newCfg.User != "" { + t.termCfg.User = newCfg.User + } + if len(newCfg.Options) > 0 { + t.termCfg.Options = newCfg.Options + } + + // Adjust terminal size + t.Resize(newCfg.Cols, newCfg.Rows) + + return &t.termCfg +} + +// Open a new terminal - execute ssh command and bind stdin/stdout to WebSocket +func (t *TermSSH) Open(sock *socketio.Socket, sessID string) (*xsapiv1.TerminalConfig, error) { + + // Get target info to retrieve IP + tgt := t.targets.Get(t.targetID) + if tgt == nil { + return nil, fmt.Errorf("Cannot retrieve target definition") + } + tgtCfg := (*tgt).GetConfig() + + // Sanity check + if tgtCfg.IP == "" { + return nil, fmt.Errorf("null target IP") + } + userStr := "" + if t.termCfg.User != "" { + userStr = t.termCfg.User + "@" + } + + // Compute ssh command + cmd := "ssh" + cmdID := "ssh_term_" + t.termCfg.ID + args := t.termCfg.Options + args = append(args, userStr+tgtCfg.IP) + + t.sshWS = eows.New(cmd, args, sock, sessID, cmdID) + t.sshWS.Log = t.Log + t.sshWS.OutSplit = eows.SplitChar + t.sshWS.PtsMode = true + + // Define callback for input (stdin) + t.sshWS.InputEvent = xsapiv1.TerminalInEvent + t.sshWS.InputCB = func(e *eows.ExecOverWS, stdin string) (string, error) { + t.Log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1)) + + // Handle Ctrl-D + if len(stdin) == 1 && stdin == "\x04" { + // Close stdin + errMsg := fmt.Errorf("close stdin: %v", stdin) + return "", errMsg + } + + return stdin, nil + } + + // Define callback for output (stdout+stderr) + t.sshWS.OutputCB = func(e *eows.ExecOverWS, stdout, stderr string) { + // IO socket can be nil when disconnected + so := t.sessions.IOSocketGet(e.Sid) + if so == nil { + t.Log.Infof("%s not emitted: WS closed (sid:%s, CmdID:%s)", xsapiv1.TerminalOutEvent, e.Sid, e.CmdID) + return + } + + // Retrieve project ID and RootPath + data := e.UserData + termID := (*data)["ID"].(string) + + t.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - termID:%s", xsapiv1.TerminalOutEvent, e.Sid[4:], e.CmdID, termID) + if stdout != "" { + t.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1)) + } + if stderr != "" { + t.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1)) + } + + // FIXME replace by .BroadcastTo a room + err := (*so).Emit(xsapiv1.TerminalOutEvent, xsapiv1.TerminalOutMsg{ + TermID: termID, + Timestamp: time.Now().String(), + Stdout: stdout, + Stderr: stderr, + }) + if err != nil { + t.Log.Errorf("WS Emit : %v", err) + } + } + + // Define callback for output + t.sshWS.ExitCB = func(e *eows.ExecOverWS, code int, err error) { + t.Log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err) + + // IO socket can be nil when disconnected + so := t.sessions.IOSocketGet(e.Sid) + if so == nil { + t.Log.Infof("%s not emitted - WS closed (id:%s)", xsapiv1.TerminalExitEvent, e.CmdID) + return + } + + // Retrieve project ID and RootPath + data := e.UserData + termID := (*data)["ID"].(string) + + // FIXME replace by .BroadcastTo a room + errSoEmit := (*so).Emit(xsapiv1.TerminalExitEvent, xsapiv1.TerminalExitMsg{ + TermID: termID, + Timestamp: time.Now().String(), + Code: code, + Error: err, + }) + if errSoEmit != nil { + t.Log.Errorf("WS Emit : %v", errSoEmit) + } + + t.termCfg.Status = xsapiv1.StatusTermClose + t.sshWS = nil + } + + // data (used within callbacks) + data := make(map[string]interface{}) + data["ID"] = t.termCfg.ID + t.sshWS.UserData = &data + + // Start ssh command + t.Log.Infof("Execute [Cmd ID %s]: %v %v", t.sshWS.CmdID, t.sshWS.Cmd, t.sshWS.Args) + + if err := t.sshWS.Start(); err != nil { + return &t.termCfg, err + } + + t.termCfg.Status = xsapiv1.StatusTermOpen + + return &t.termCfg, nil +} + +// Close a terminal +func (t *TermSSH) Close() (*xsapiv1.TerminalConfig, error) { + // nothing to do when not open + if t.termCfg.Status != xsapiv1.StatusTermOpen { + return &t.termCfg, nil + } + + err := t.sshWS.Signal("SIGTERM") + + return &t.termCfg, err +} + +// Resize a terminal +func (t *TermSSH) Resize(cols, rows uint16) (*xsapiv1.TerminalConfig, error) { + if t.sshWS == nil { + return &t.termCfg, fmt.Errorf("ssh session not initialized") + } + + if cols > 0 { + t.termCfg.Cols = cols + } + if rows > 0 { + t.termCfg.Rows = rows + } + + err := t.sshWS.TerminalSetSize(t.termCfg.Rows, t.termCfg.Cols) + if err != nil { + t.Log.Errorf("Error ssh TerminalSetSize: %v", err) + } + + return &t.termCfg, err +} + +// Signal Send a signal to a terminal +func (t *TermSSH) Signal(sigName string) error { + return t.sshWS.Signal(sigName) +} |