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/eows"
26 "gerrit.automotivelinux.org/gerrit/src/xds/xds-server.git/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.PtyMode = true
133 // Define callback for input (stdin)
134 t.sshWS.InputEvent = xsapiv1.TerminalInEvent
135 t.sshWS.InputCB = func(e *eows.ExecOverWS, stdin []byte) ([]byte, error) {
137 t.Log.Debugf("STDIN <<%v>> %s", stdin, string(stdin))
142 // Define callback for output (stdout+stderr)
143 t.sshWS.OutputCB = func(e *eows.ExecOverWS, stdout, stderr []byte) {
144 // IO socket can be nil when disconnected
145 so := t.sessions.IOSocketGet(e.Sid)
147 t.Log.Infof("%s not emitted: WS closed (sid:%s, CmdID:%s)", xsapiv1.TerminalOutEvent, e.Sid, e.CmdID)
151 // Retrieve project ID and RootPath
153 termID := (*data)["ID"].(string)
156 t.Log.Debugf("%s emitted - WS sid[4:] %s - id:%s - termID:%s", xsapiv1.TerminalOutEvent, e.Sid[4:], e.CmdID, termID)
158 t.Log.Debugf("STDOUT <<%v>>", strings.Replace(string(stdout), "\n", "\\n", -1))
161 t.Log.Debugf("STDERR <<%v>>", strings.Replace(string(stderr), "\n", "\\n", -1))
165 // FIXME replace by .BroadcastTo a room
166 err := (*so).Emit(xsapiv1.TerminalOutEvent, xsapiv1.TerminalOutMsg{
168 Timestamp: time.Now().String(),
173 t.Log.Errorf("WS Emit : %v", err)
177 // Define callback for output
178 t.sshWS.ExitCB = func(e *eows.ExecOverWS, code int, err error) {
179 t.Log.Debugf("Command [Cmd ID %s] exited: code %d, error: %v", e.CmdID, code, err)
181 // IO socket can be nil when disconnected
182 so := t.sessions.IOSocketGet(e.Sid)
184 t.Log.Infof("%s not emitted - WS closed (id:%s)", xsapiv1.TerminalExitEvent, e.CmdID)
188 // Retrieve project ID and RootPath
190 termID := (*data)["ID"].(string)
192 // FIXME replace by .BroadcastTo a room
193 errSoEmit := (*so).Emit(xsapiv1.TerminalExitEvent, xsapiv1.TerminalExitMsg{
195 Timestamp: time.Now().String(),
199 if errSoEmit != nil {
200 t.Log.Errorf("WS Emit : %v", errSoEmit)
203 t.termCfg.Status = xsapiv1.StatusTermClose
207 // data (used within callbacks)
208 data := make(map[string]interface{})
209 data["ID"] = t.termCfg.ID
210 t.sshWS.UserData = &data
213 t.Log.Infof("Execute [Cmd ID %s]: %v %v", t.sshWS.CmdID, t.sshWS.Cmd, t.sshWS.Args)
215 if err := t.sshWS.Start(); err != nil {
216 return &t.termCfg, err
219 t.termCfg.Status = xsapiv1.StatusTermOpen
221 return &t.termCfg, nil
225 func (t *TermSSH) Close() (*xsapiv1.TerminalConfig, error) {
226 // nothing to do when not open or closing
227 if !(t.termCfg.Status == xsapiv1.StatusTermOpen || t.termCfg.Status == xsapiv1.StatusTermClosing) {
228 return &t.termCfg, nil
231 err := t.sshWS.Signal("SIGTERM")
233 t.termCfg.Status = xsapiv1.StatusTermClosing
235 return &t.termCfg, err
239 func (t *TermSSH) Resize(cols, rows uint16) (*xsapiv1.TerminalConfig, error) {
241 return &t.termCfg, fmt.Errorf("ssh session not initialized")
245 t.termCfg.Cols = cols
248 t.termCfg.Rows = rows
251 t.LogSillyf("Terminal resize id=%v, cols=%v, rows=%v", t.termCfg.ID, cols, rows)
253 err := t.sshWS.TerminalSetSize(t.termCfg.Rows, t.termCfg.Cols)
255 t.Log.Errorf("Error ssh TerminalSetSize: %v", err)
258 return &t.termCfg, err
261 // Signal Send a signal to a terminal
262 func (t *TermSSH) Signal(sigName string) error {
263 return t.sshWS.Signal(sigName)