2 * Copyright (C) 2018 "IoT.bzh"
3 * Author Sebastien Douheret <sebastien@iot.bzh>
5 * Licensed under the Apache License, Version 2.0 (the "License");
6 * you may not use this file except in compliance with the License.
7 * You may obtain a copy of the License at
9 * http://www.apache.org/licenses/LICENSE-2.0
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
25 "gerrit.automotivelinux.org/gerrit/src/xds/xds-common.git/golib/eows"
26 "gerrit.automotivelinux.org/gerrit/src/xds/xds-server/lib/xsapiv1"
27 socketio "github.com/googollee/go-socket.io"
28 uuid "github.com/satori/go.uuid"
31 // ITARGET interface implementation for standard targets
36 termCfg xsapiv1.TerminalConfig
38 sshWS *eows.ExecOverWS
41 // NewTermSSH Create a new instance of TermSSH
42 func NewTermSSH(ctx *Context, cfg xsapiv1.TerminalConfig, targetID string) *TermSSH {
44 // Allocate and set default settings
47 termCfg: xsapiv1.TerminalConfig{
50 Type: xsapiv1.TypeTermSSH,
51 Status: xsapiv1.StatusTermClose,
53 Options: []string{""},
65 func (t *TermSSH) _NewUID(suffix string) string {
66 uuid := uuid.NewV1().String()
73 // GetConfig Get public part of terminal config
74 func (t *TermSSH) GetConfig() xsapiv1.TerminalConfig {
78 // UpdateConfig Update terminal config
79 func (t *TermSSH) UpdateConfig(newCfg xsapiv1.TerminalConfig) *xsapiv1.TerminalConfig {
81 if t.termCfg.ID == "" {
83 t.termCfg.ID = newCfg.ID
85 t.termCfg.ID = t._NewUID("")
88 if newCfg.Name != "" {
89 t.termCfg.Name = newCfg.Name
91 if newCfg.User != "" {
92 t.termCfg.User = newCfg.User
94 if len(newCfg.Options) > 0 {
95 t.termCfg.Options = newCfg.Options
98 // Adjust terminal size
99 t.Resize(newCfg.Cols, newCfg.Rows)
104 // Open a new terminal - execute ssh command and bind stdin/stdout to WebSocket
105 func (t *TermSSH) Open(sock *socketio.Socket, sessID string) (*xsapiv1.TerminalConfig, error) {
107 // Get target info to retrieve IP
108 tgt := t.targets.Get(t.targetID)
110 return nil, fmt.Errorf("Cannot retrieve target definition")
112 tgtCfg := (*tgt).GetConfig()
116 return nil, fmt.Errorf("null target IP")
119 if t.termCfg.User != "" {
120 userStr = t.termCfg.User + "@"
123 // Compute ssh command
125 cmdID := "ssh_term_" + t.termCfg.ID
126 args := t.termCfg.Options
127 args = append(args, userStr+tgtCfg.IP)
129 t.sshWS = eows.New(cmd, args, sock, sessID, cmdID)
131 t.sshWS.OutSplit = eows.SplitChar
132 t.sshWS.PtsMode = true
134 // Define callback for input (stdin)
135 t.sshWS.InputEvent = xsapiv1.TerminalInEvent
136 t.sshWS.InputCB = func(e *eows.ExecOverWS, stdin string) (string, error) {
137 t.Log.Debugf("STDIN <<%v>>", strings.Replace(stdin, "\n", "\\n", -1))
140 if len(stdin) == 1 && stdin == "\x04" {
142 errMsg := fmt.Errorf("close stdin: %v", stdin)
149 // Define callback for output (stdout+stderr)
150 t.sshWS.OutputCB = func(e *eows.ExecOverWS, stdout, stderr string) {
151 // IO socket can be nil when disconnected
152 so := t.sessions.IOSocketGet(e.Sid)
154 t.Log.Infof("%s not emitted: WS closed (sid:%s, CmdID:%s)", xsapiv1.TerminalOutEvent, e.Sid, e.CmdID)
158 // Retrieve project ID and RootPath
160 termID := (*data)["ID"].(string)
162 t.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - termID:%s", xsapiv1.TerminalOutEvent, e.Sid[4:], e.CmdID, termID)
164 t.Log.Debugf("STDOUT <<%v>>", strings.Replace(stdout, "\n", "\\n", -1))
167 t.Log.Debugf("STDERR <<%v>>", strings.Replace(stderr, "\n", "\\n", -1))
170 // FIXME replace by .BroadcastTo a room
171 err := (*so).Emit(xsapiv1.TerminalOutEvent, xsapiv1.TerminalOutMsg{
173 Timestamp: time.Now().String(),
178 t.Log.Errorf("WS Emit : %v", err)
182 // Define callback for output
183 t.sshWS.ExitCB = func(e *eows.ExecOverWS, code int, err error) {
184 t.Log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err)
186 // IO socket can be nil when disconnected
187 so := t.sessions.IOSocketGet(e.Sid)
189 t.Log.Infof("%s not emitted - WS closed (id:%s)", xsapiv1.TerminalExitEvent, e.CmdID)
193 // Retrieve project ID and RootPath
195 termID := (*data)["ID"].(string)
197 // FIXME replace by .BroadcastTo a room
198 errSoEmit := (*so).Emit(xsapiv1.TerminalExitEvent, xsapiv1.TerminalExitMsg{
200 Timestamp: time.Now().String(),
204 if errSoEmit != nil {
205 t.Log.Errorf("WS Emit : %v", errSoEmit)
208 t.termCfg.Status = xsapiv1.StatusTermClose
212 // data (used within callbacks)
213 data := make(map[string]interface{})
214 data["ID"] = t.termCfg.ID
215 t.sshWS.UserData = &data
218 t.Log.Infof("Execute [Cmd ID %s]: %v %v", t.sshWS.CmdID, t.sshWS.Cmd, t.sshWS.Args)
220 if err := t.sshWS.Start(); err != nil {
221 return &t.termCfg, err
224 t.termCfg.Status = xsapiv1.StatusTermOpen
226 return &t.termCfg, nil
230 func (t *TermSSH) Close() (*xsapiv1.TerminalConfig, error) {
231 // nothing to do when not open
232 if t.termCfg.Status != xsapiv1.StatusTermOpen {
233 return &t.termCfg, nil
236 err := t.sshWS.Signal("SIGTERM")
238 return &t.termCfg, err
242 func (t *TermSSH) Resize(cols, rows uint16) (*xsapiv1.TerminalConfig, error) {
244 return &t.termCfg, fmt.Errorf("ssh session not initialized")
248 t.termCfg.Cols = cols
251 t.termCfg.Rows = rows
254 err := t.sshWS.TerminalSetSize(t.termCfg.Rows, t.termCfg.Cols)
256 t.Log.Errorf("Error ssh TerminalSetSize: %v", err)
259 return &t.termCfg, err
262 // Signal Send a signal to a terminal
263 func (t *TermSSH) Signal(sigName string) error {
264 return t.sshWS.Signal(sigName)