package ssh import ( "fmt" "os/exec" "strings" "github.com/sirupsen/logrus" ) // HostConfig is a SSH host config. type HostConfig struct { Host string IdentityFile string Port string User string } // String presents a human friendly output for the HostConfig. func (h HostConfig) String() string { return fmt.Sprintf( "{host: %s, username: %s, port: %s, identityfile: %s}", h.Host, h.User, h.Port, h.IdentityFile, ) } // GetHostConfig retrieves a ~/.ssh/config config for a host using /usr/bin/ssh // directly. We therefore maintain consistent interop with this standard // tooling. This is useful because SSH confuses a lot of people and having to // learn how two tools (`ssh` and `abra`) handle SSH connection details instead // of one (just `ssh`) is Not Cool. Here's to less bug reports on this topic! func GetHostConfig(hostname string) (HostConfig, error) { var hostConfig HostConfig out, err := exec.Command("ssh", "-G", hostname).Output() if err != nil { return hostConfig, err } for _, line := range strings.Split(string(out), "\n") { entries := strings.Split(line, " ") for idx, entry := range entries { if entry == "hostname" { hostConfig.Host = entries[idx+1] } if entry == "user" { hostConfig.User = entries[idx+1] } if entry == "port" { hostConfig.Port = entries[idx+1] } if entry == "identityfile" { if hostConfig.IdentityFile == "" { hostConfig.IdentityFile = entries[idx+1] } } } } logrus.Debugf("retrieved ssh config for %s: %s", hostname, hostConfig.String()) return hostConfig, nil } // Fatal is a error output wrapper which aims to make SSH failures easier to // parse through re-wording. func Fatal(hostname string, err error) error { out := err.Error() if strings.Contains(out, "Host key verification failed.") { return fmt.Errorf("SSH host key verification failed for %s", hostname) } else if strings.Contains(out, "Could not resolve hostname") { return fmt.Errorf("could not resolve hostname for %s", hostname) } else if strings.Contains(out, "Connection timed out") { return fmt.Errorf("connection timed out for %s", hostname) } else if strings.Contains(out, "Permission denied") { return fmt.Errorf("ssh auth: permission denied for %s", hostname) } else if strings.Contains(out, "Network is unreachable") { return fmt.Errorf("unable to connect to %s, network is unreachable?", hostname) } else { return err } }