blob: a8e6ba27dfa85be8eff2cab77675b90fcd1c904a [file] [log] [blame]
// Copyright 2020 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"context"
"errors"
"flag"
"fmt"
"log"
"os"
"os/exec"
"go.fuchsia.dev/fuchsia/tools/lib/color"
"go.fuchsia.dev/fuchsia/tools/lib/logger"
"go.fuchsia.dev/fuchsia/tools/sdk-tools/sdkcommon"
"github.com/google/subcommands"
)
var (
// ExecCommand exports exec.Command as a variable so it can be mocked.
ExecCommand = exec.Command
)
const (
privateKeyFlag = "private-key"
deviceNameFlag = "device-name"
deviceIPFlag = "device-ip"
sshConfigFlag = "sshconfig"
dataPathFlag = "data-path"
logLevelFlag = "level"
verboseFlag = "verbose"
logFlags = log.Ltime
)
type fsshCmd struct {
// Target related options.
privateKey string
deviceName string
deviceIP string
sshConfig string
dataPath string
logLevel logger.LogLevel
verbose bool
}
func (*fsshCmd) Name() string { return "fssh" }
func (*fsshCmd) Synopsis() string {
return "Creates an SSH connection with a device and executes a command."
}
func (*fsshCmd) Usage() string {
return fmt.Sprintf(`fssh [-%s device-name -%s device-ip -%s private-key -%s sshconfig -%s data-path -%s -%s log-level] [ssh_command]
Subcommands:
sync-keys Sync SSH key files associated with Fuchsia between a local and remote workstation.
tunnel Creates a tunnel between a local Fuchsia device and a remote host
`, deviceNameFlag, deviceIPFlag, privateKeyFlag, sshConfigFlag, dataPathFlag, verboseFlag, logLevelFlag)
}
func (c *fsshCmd) SetFlags(f *flag.FlagSet) {
c.logLevel = logger.InfoLevel // Default that may be overridden.
f.StringVar(&c.privateKey, privateKeyFlag, "", "Uses additional private key when using ssh to access the device.")
f.StringVar(&c.deviceName, deviceNameFlag, "", `Serves packages to a device with the given device hostname. Cannot be used with --device-ip."
If neither --device-name nor --device-ip are specified, the device-name configured using ffx is used.`)
f.StringVar(&c.deviceIP, deviceIPFlag, "", `Serves packages to a device with the given device ip address. Cannot be used with --device-name."
If neither --device-name nor --device-ip are specified, the device-name configured using ffx is used.`)
f.StringVar(&c.sshConfig, sshConfigFlag, "", "Use the specified sshconfig file instead of fssh's version.")
f.StringVar(&c.dataPath, dataPathFlag, "", "Specifies the data path for SDK tools. Defaults to $HOME/.fuchsia")
f.Var(&c.logLevel, logLevelFlag, "Output verbosity, can be fatal, error, warning, info, debug or trace.")
f.BoolVar(&c.verbose, verboseFlag, false, "Runs ssh in verbose mode.")
}
func (c *fsshCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
// Write all logs to stderr. Other tools parse the output of fssh which will break if logs
// are written to stdout.
log := logger.NewLogger(c.logLevel, color.NewColor(color.ColorAuto), os.Stderr, os.Stderr, "fssh ")
log.SetFlags(logFlags)
sdk, err := sdkcommon.NewWithDataPath(c.dataPath)
if err != nil {
log.Fatalf("Could not initialize SDK %v", err)
}
deviceConfig, err := sdk.ResolveTargetAddress(c.deviceIP, c.deviceName)
if err != nil {
log.Fatalf("%v", err)
}
log.Debugf("Using target address: %s", deviceConfig.DeviceIP)
// If no deviceIPFlag was given, then get the SSH Port from the configuration.
// We can't look at the configuration if the ip address was passed in since we don't have the
// device name which is needed to look up the property.
sshPort := ""
if c.deviceIP == "" {
sshPort = deviceConfig.SSHPort
log.Debugf("Using sshport address: %s", sshPort)
if sshPort == "22" {
sshPort = ""
}
}
log.Debugf("Running SSH with %s %s %s %s %t %s ", deviceConfig.DeviceIP, c.sshConfig,
c.privateKey, sshPort, c.verbose, f.Args())
if err := sdk.RunSSHShell(deviceConfig.DeviceIP, c.sshConfig, c.privateKey, sshPort, c.verbose, f.Args()); err != nil {
var exitError *exec.ExitError
// If there is an exit code, exit the subcommand with the same exit code.
if errors.As(err, &exitError) {
log.Errorf("Error running ssh: %v", err)
os.Exit(exitError.ExitCode())
} else {
log.Fatalf("Error running ssh: %v", err)
}
}
return subcommands.ExitSuccess
}