forked from toolshed/abra
.gitea
cli
cmd
pkg
app
autocomplete
catalogue
client
compose
config
container
context
dns
formatter
git
integration
jsontable
limit
lint
recipe
runtime
secret
server
service
ssh
ssh.go
test
upstream
web
scripts
tests
.dockerignore
.drone.yml
.e2e.env.sample
.envrc.sample
.gitignore
.goreleaser.yml
AUTHORS.md
Dockerfile
LICENSE
Makefile
README.md
go.mod
go.sum
renovate.json
86 lines
2.4 KiB
Go
86 lines
2.4 KiB
Go
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
|
|
}
|
|
}
|