summaryrefslogtreecommitdiffstats
path: root/lib/xdsserver/terminal-ssh.go
diff options
context:
space:
mode:
Diffstat (limited to 'lib/xdsserver/terminal-ssh.go')
-rw-r--r--lib/xdsserver/terminal-ssh.go265
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)
+}