blob: 74947787c9b4c37add965866977e6ba5937eb38f [file] [log] [blame]
// Copyright 2021 The ChromiumOS Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package ssh
import (
"bytes"
"context"
"fmt"
"log"
"golang.org/x/crypto/ssh"
)
// RunResult represents result of executed command.
type RunResult struct {
// Command executed on the resource.
Command string
// Exit code return.
// Eg: 0 - everything is good
// 1 - executed stop with error code `1`
// 15 - timeout of execution
ExitCode int
// Standard output
Stdout string
// Standard error output
Stderr string
}
// Run executes command on the target address by SSH.
func Run(ctx context.Context, pool *Pool, addr string, cmd string) (result *RunResult) {
result = &RunResult{
Command: cmd,
ExitCode: -1,
}
if pool == nil {
result.Stderr = "run SSH: pool is not initialized"
return
} else if addr == "" {
result.Stderr = "run SSH: addr is empty"
return
} else if cmd == "" {
result.Stderr = fmt.Sprintf("run SSH %q: cmd is empty", addr)
return
}
sc, err := pool.GetContext(ctx, addr)
if err != nil {
result.Stderr = fmt.Sprintf("run SSH %q: fail to get client from pool; %s", addr, err)
return
}
defer func() { pool.Put(addr, sc) }()
result = internalRunSSH(cmd, sc)
log.Println(ctx, "run SSH %q: Cmd: %q; ExitCode: %d; Stdout: %q; Stderr: %q", addr, result.Command, result.ExitCode, result.Stdout, result.Stderr)
return
}
func internalRunSSH(cmd string, client *ssh.Client) (result *RunResult) {
result = &RunResult{
Command: cmd,
ExitCode: -1,
}
session, err := client.NewSession()
if err != nil {
result.Stderr = fmt.Sprintf("internal run SSH: %s", err)
return
}
defer func() { session.Close() }()
var stdOut, stdErr bytes.Buffer
session.Stdout = &stdOut
session.Stderr = &stdErr
err = session.Run(cmd)
result.Stdout = stdOut.String()
result.Stderr = stdErr.String()
if err == nil {
result.ExitCode = 0
} else if exitErr, ok := err.(*ssh.ExitError); ok {
result.ExitCode = exitErr.ExitStatus()
} else if _, ok := err.(*ssh.ExitMissingError); ok {
result.ExitCode = -2
result.Stderr = err.Error()
} else {
// Set error 1 as not expected exit.
result.ExitCode = -3
result.Stderr = err.Error()
}
return
}