|
46 | 46 | type SSHRunner struct {
|
47 | 47 | d drivers.Driver
|
48 | 48 | c *ssh.Client
|
| 49 | + s *ssh.Session |
49 | 50 | }
|
50 | 51 |
|
51 | 52 | // NewSSHRunner returns a new SSHRunner that will run commands
|
@@ -194,6 +195,100 @@ func (s *SSHRunner) RunCmd(cmd *exec.Cmd) (*RunResult, error) {
|
194 | 195 | return rr, fmt.Errorf("%s: %v\nstdout:\n%s\nstderr:\n%s", rr.Command(), err, rr.Stdout.String(), rr.Stderr.String())
|
195 | 196 | }
|
196 | 197 |
|
| 198 | +// teeSSHStart starts a non-blocking SSH command, streaming stdout, stderr to logs |
| 199 | +func teeSSHStart(s *ssh.Session, cmd string, outB io.Writer, errB io.Writer) error { |
| 200 | + outPipe, err := s.StdoutPipe() |
| 201 | + if err != nil { |
| 202 | + return errors.Wrap(err, "stdout") |
| 203 | + } |
| 204 | + |
| 205 | + errPipe, err := s.StderrPipe() |
| 206 | + if err != nil { |
| 207 | + return errors.Wrap(err, "stderr") |
| 208 | + } |
| 209 | + |
| 210 | + go func() { |
| 211 | + if err := teePrefix(ErrPrefix, errPipe, errB, klog.V(8).Infof); err != nil { |
| 212 | + klog.Errorf("tee stderr: %v", err) |
| 213 | + } |
| 214 | + }() |
| 215 | + go func() { |
| 216 | + if err := teePrefix(OutPrefix, outPipe, outB, klog.V(8).Infof); err != nil { |
| 217 | + klog.Errorf("tee stdout: %v", err) |
| 218 | + } |
| 219 | + }() |
| 220 | + |
| 221 | + return s.Start(cmd) |
| 222 | +} |
| 223 | + |
| 224 | +// StartCmd implements the Command Runner interface to start a exec.Cmd object |
| 225 | +func (s *SSHRunner) StartCmd(cmd *exec.Cmd) (*StartedCmd, error) { |
| 226 | + if cmd.Stdin != nil { |
| 227 | + return nil, fmt.Errorf("SSHRunner does not support stdin - you could be the first to add it") |
| 228 | + } |
| 229 | + |
| 230 | + if s.s != nil { |
| 231 | + return nil, fmt.Errorf("another SSH command has been started and is currently running") |
| 232 | + } |
| 233 | + |
| 234 | + rr := &RunResult{Args: cmd.Args} |
| 235 | + sc := &StartedCmd{cmd: cmd, rr: rr} |
| 236 | + klog.Infof("Start: %v", rr.Command()) |
| 237 | + |
| 238 | + var outb, errb io.Writer |
| 239 | + |
| 240 | + if cmd.Stdout == nil { |
| 241 | + var so bytes.Buffer |
| 242 | + outb = io.MultiWriter(&so, &rr.Stdout) |
| 243 | + } else { |
| 244 | + outb = io.MultiWriter(cmd.Stdout, &rr.Stdout) |
| 245 | + } |
| 246 | + |
| 247 | + if cmd.Stderr == nil { |
| 248 | + var se bytes.Buffer |
| 249 | + errb = io.MultiWriter(&se, &rr.Stderr) |
| 250 | + } else { |
| 251 | + errb = io.MultiWriter(cmd.Stderr, &rr.Stderr) |
| 252 | + } |
| 253 | + |
| 254 | + sess, err := s.session() |
| 255 | + if err != nil { |
| 256 | + return sc, errors.Wrap(err, "NewSession") |
| 257 | + } |
| 258 | + |
| 259 | + s.s = sess |
| 260 | + |
| 261 | + err = teeSSHStart(s.s, shellquote.Join(cmd.Args...), outb, errb) |
| 262 | + |
| 263 | + return sc, err |
| 264 | +} |
| 265 | + |
| 266 | +// WaitCmd implements the Command Runner interface to wait until a started exec.Cmd object finishes |
| 267 | +func (s *SSHRunner) WaitCmd(sc *StartedCmd) (*RunResult, error) { |
| 268 | + if s.s == nil { |
| 269 | + return nil, fmt.Errorf("there is no SSH command started") |
| 270 | + } |
| 271 | + |
| 272 | + rr := sc.rr |
| 273 | + |
| 274 | + err := s.s.Wait() |
| 275 | + if exitError, ok := err.(*exec.ExitError); ok { |
| 276 | + rr.ExitCode = exitError.ExitCode() |
| 277 | + } |
| 278 | + |
| 279 | + if err := s.s.Close(); err != io.EOF { |
| 280 | + klog.Errorf("session close: %v", err) |
| 281 | + } |
| 282 | + |
| 283 | + s.s = nil |
| 284 | + |
| 285 | + if err == nil { |
| 286 | + return rr, nil |
| 287 | + } |
| 288 | + |
| 289 | + return rr, fmt.Errorf("%s: %v\nstdout:\n%s\nstderr:\n%s", rr.Command(), err, rr.Stdout.String(), rr.Stderr.String()) |
| 290 | +} |
| 291 | + |
197 | 292 | // Copy copies a file to the remote over SSH.
|
198 | 293 | func (s *SSHRunner) Copy(f assets.CopyableFile) error {
|
199 | 294 | dst := path.Join(path.Join(f.GetTargetDir(), f.GetTargetName()))
|
|
0 commit comments