func getKeyFile(keyfile string) (ssh.Signer, error) {
	buf, err := ioutil.ReadFile(keyfile)
	SoftCheck(err)
	key, err := ssh.ParsePrivateKey(buf)
	SoftCheck(err)
	return key, nil
}
getKeyFile provides an ssh.Signer for the given keyfile (path to a private key).
package util
import (
	"errors"
	"flag"
	"fmt"
	"io/ioutil"
	"log"
	"net/http"
	"regexp"
	"strings"
	"sync"
	"time"
	"golang.org/x/crypto/ssh"
	"gopkg.in/yaml.v2"
)
type Config struct {
	Version string         `yaml:"version"`
	Scripts []ScriptConfig `yaml:"scripts"`
}
type ScriptConfig struct {
	Name          string             `yaml:"name"`
	Script        string             `yaml:"script"`
	Timeout       string             `yaml:"timeout"`
	Pattern       string             `yaml:"pattern"`
	Credentials   []CredentialConfig `yaml:"credentials"`
	ParsedTimeout time.Duration       
	Ignored       bool                
}
type CredentialConfig struct {
	Host               string `yaml:"host"`
	Port               string `yaml:"port"`
	User               string `yaml:"user"`
	KeyFile            string `yaml:"keyfile"`
	ScriptResult       string  
	ScriptReturnCode   int     
	ScriptError        string  
	ResultPatternMatch int8    
}
func FatalCheck(e error) {
	if e != nil {
		log.Fatal("error: ", e)
	}
}
func SoftCheck(e error) bool {
	if e != nil {
		LogMsg(fmt.Sprintf("%v", e))
		return true
	} else {
		return false
	}
}
func LogMsg(s string) {
	log.Printf("ssh_exporter :: %s", fmt.Sprintf("%s", s))
}
func ParseFlags(c, p *string) (*string, *string) {
	flag.StringVar(c, "config", "config.yml", "Path to your ssh_exporter config file")
	flag.StringVar(p, "port", "9428", "Port probed metrics are served on.")
	flag.Parse()
	return c, p
}
func ParseConfig(c string) (Config, error) {
	raw, err := ioutil.ReadFile(c)
	FatalCheck(err)
	initialConfig := Config{}
	err = yaml.Unmarshal([]byte(raw), &initialConfig)
	SoftCheck(err)
	finalConfig, err := adjustConfig(initialConfig)
	SoftCheck(err)
	return finalConfig, err
}
func ParseQuery(w http.ResponseWriter, r *http.Request) (*regexp.Regexp, error) {
	if r.URL.Query().Get("pattern") == "" {
		return nil, errors.New("Probe endpoint was hit, but pattern parameter was not passed.")
	}
	p, err := regexp.Compile(string(r.URL.Query().Get("pattern")))
	return p, err
}
func BatchExecute(c *Config, p *regexp.Regexp) (Config, error) {
	var done sync.WaitGroup
	t := make(chan bool)
	for i, v := range c.Scripts {
		if p.MatchString(v.Name) != true {
			c.Scripts[i].Ignored = true
		} else {
			go executeScript(v.Script, v.Pattern, &c.Scripts[i].Credentials, &done, t)
		}
	}
	done.Wait()
	for _, v := range c.Scripts {
		if !v.Ignored {
			for _, _ = range v.Credentials {
				select {
				case <-time.After(v.ParsedTimeout):
				case <-t:
				}
			}
		}
	}
	return *c, nil
}
func PrometheusFormatResponse(c Config) (string, error) {
	var response string
	exitStatusFormatStr := "ssh_exporter_%s_exit_status{name=\"%s\",host=\"%s\",user=\"%s\",script=\"%s\",exit_status=\"%d\"} %d"
	patternMatchFormatStr := "ssh_exporter_%s_pattern_match{name=\"%s\",host=\"%s\",user=\"%s\",script=\"%s\",regex=\"%s\"} %d"
	exitStatusHelpStr := "# HELP ssh_exporter_%s_exit_status Integer exit status of commands and metadata about the command's execution.\n# TYPE ssh_exporter gauge"
	patternMatchHelpStr := "# HELP ssh_exporter_%s_pattern_match Boolean match of regex on output of script of commands and metadata about the command's execution.\n# TYPE ssh_exporter gauge"
	for _, i := range c.Scripts {
		if i.Ignored != true {
			exitedDoc := fmt.Sprintf(exitStatusHelpStr, i.Name)
			matchedDoc := fmt.Sprintf(patternMatchHelpStr, i.Name)
			response = fmt.Sprintf("%s%s", response, exitedDoc)
			for _, j := range i.Credentials {
				s := fmt.Sprintf(exitStatusFormatStr, i.Name, i.Name, j.Host, j.User, i.Script, j.ScriptReturnCode, j.ScriptReturnCode)
				response = fmt.Sprintf("%s\n%s", response, s)
			}
			response = fmt.Sprintf("%s\n%s", response, matchedDoc)
			for _, j := range i.Credentials {
				m := fmt.Sprintf(patternMatchFormatStr, i.Name, i.Name, j.Host, j.User, i.Script, i.Pattern, j.ResultPatternMatch)
				response = fmt.Sprintf("%s\n%s", response, m)
			}
			response = fmt.Sprintf("%s\n", response)
		}
	}
	return response, nil
}
func adjustConfig(c Config) (Config, error) {
	for c_i, v_i := range c.Scripts {
		for c_j, v_j := range v_i.Credentials {
			if v_j.Port == "" {
				c.Scripts[c_i].Credentials[c_j].Port = "22"
			}
		}
		tmp, err := time.ParseDuration(c.Scripts[c_i].Timeout)
		if !SoftCheck(err) {
			c.Scripts[c_i].ParsedTimeout = tmp
		} else {
			LogMsg(fmt.Sprintf("Failed to parse `timeout` for %s. Default to 10s", c.Scripts[c_i].Name))
			c.Scripts[c_i].ParsedTimeout, _ = time.ParseDuration("10s")
		}
	}
	return c, nil
}
func executeScript(script, pattern string, creds *[]CredentialConfig, done *sync.WaitGroup, t chan bool) {
	match, _ := regexp.Compile(pattern)
	for i, c := range *creds {
		done.Add(1)
		go func() {
			result, status, err := executeScriptOnHost(c.Host, c.Port, c.User, c.KeyFile, script)
			(*creds)[i].ScriptReturnCode = status
			(*creds)[i].ScriptResult = result
			if err != nil {
				(*creds)[i].ScriptError = fmt.Sprintf("%v", err)
			}
			if match.MatchString(result) {
				(*creds)[i].ResultPatternMatch = 1
			} else {
				(*creds)[i].ResultPatternMatch = 0
			}
			t <- true
			done.Done()
		}()
	}
}
func executeScriptOnHost(host, port, user, keyfile, script string) (string, int, error) {
	client, session, err := sshConnectToHost(host, port, user, keyfile)
	if SoftCheck(err) {
		return "", -1, err
	}
	out, err := session.CombinedOutput(script)
	if SoftCheck(err) {
		var errorStatusCode int
		fmt.Sscanf(fmt.Sprintf("%v", err), "Process exited with status %d", &errorStatusCode)
		if errorStatusCode != 0 {
			return "", errorStatusCode, err
		} else {
			return "", -1, err
		}
	}
	defer client.Close()
	return literalFormat(string(out)), 0, nil
}
func sshConnectToHost(host, port, user, keyfile string) (*ssh.Client, *ssh.Session, error) {
	key, err := getKeyFile(keyfile)
	SoftCheck(err)
	sshConfig := &ssh.ClientConfig{
		User: user,
		Auth: []ssh.AuthMethod{
			ssh.PublicKeys(key),
		},
		HostKeyCallback: ssh.InsecureIgnoreHostKey(),
	}
	sshConfig.SetDefaults()
	fullHost := fmt.Sprintf("%s:%s", host, port)
	client, err := ssh.Dial("tcp", fullHost, sshConfig)
	if err != nil {
		return nil, nil, err
	}
	session, err := client.NewSession()
	if err != nil {
		return nil, nil, err
	}
	return client, session, nil
}