こんにちは。かなり久々に書きます。
今は割と大きめの会社でSREをメインにやっていて、
パフォーマンス改善だったり、監視項目の整理・追加etcをしています。
昔はサーバサイドをメインでやっていて、その後android/iosアプリ開発、react+reduxとかやっていました。
今回のエントリーはsshコマンドでログインする際に基本的にconfigに鍵のパスとかを設定して後は、ssh Host名
とかでログインするのが一般的かと思います。
ただそのconfigが増えてくると、Hostの名前を何にしたか忘れてしまってわざわざconfigの中身を表示するとかめんどいなーと思い、
今回はgoでいつもssh configの名前を忘れてしまう問題をOSSで解決してみました。
コードのアドバイスでも編集依頼でもなんでもいいので、何かありましたらコメントください!!
github
インストール
brew
brew tap revenue-hack/sshcut
brew install sshcut
go get
go get
go get github.com/revenue-hack/homebrew-sshcut
使い方
.ssh/configの中身が
Host test
HostName 11.111.11.111
User test
Port 21
PreferredAuthentications publickey
IdentityFile ~/.ssh/test.pem
Host test1
HostName 22.222.22.222
User test2
Port 21
PreferredAuthentications publickey
IdentityFile ~/.ssh/test2.pem
な感じで、Hostが正しく記載されていて、
本来のやり方通り
ssh test
みたくssh+Host
でログインできれば使えます。
使い方は
sshcut
とコマンドを叩くと
number Host
0 test
1 test1
select ssh host number?:
のようにHostごとにnumberが振られているので、
後はnumberを選んでenterをするだけで、ログインできます。
コードの簡単な説明
コードは単純でmain.goのみでできています。
main関数のはじめにやっていることは、
configパスから、configファイルを読み込んでいます。
configFilePath, err := getConfigFilePath()
if !err {
fmt.Println(".ssh config file nothing Error")
os.Exit(1)
}
実際にファイルを読み込んでいるのはreadFile関数です。
func readFile(configFilePath string) (HostList, bool) {
file, err := os.Open(configFilePath)
if err != nil {
fmt.Printf("%s is not exist\n", configFilePath)
os.Exit(1)
}
defer file.Close()
reader := bufio.NewReaderSize(file, 4096)
var hosts HostList
for {
line, _, err := reader.ReadLine()
if err == io.EOF {
break
} else if err != nil {
fmt.Println("can't read config file")
panic(err)
}
host, hasHostObject := objectMapping((string(line)))
if !hasHostObject {
continue
}
hosts.Hosts = append(hosts.Hosts, host)
}
if hosts.Hosts == nil {
return hosts, false
}
return hosts, true
}
その際に、objectMapping関数でやっているのはすでにssh configはログインできる前提で、Host情報だけをマッピングしています。
func objectMapping(fileArgs string) (Host, bool) {
var alias Host
if strings.Contains(fileArgs, "Host ") {
args := strings.Fields(fileArgs)
if len(args) != 2 {
return alias, false
}
alias.Alias = args[1]
return alias, true
}
return alias, false
}
main.goの21行目で、Hostが正しく存在したら、その一覧を表示しています。
fmt.Printf("%s\t%s\n", "number", "Host")
for i, host := range hosts.Hosts {
fmt.Printf("%d\t%v\n", i, host.Alias)
}
fmt.Printf("%s", "select ssh host number?: ")
27行目からは、ユーザのnumber入力があった場合の処理を記述していて、39行目で、実際にコマンドを実行しています。
stdIn := bufio.NewScanner(os.Stdin)
if stdIn.Scan() {
text := stdIn.Text()
key, err := strconv.Atoi(text)
if err != nil {
fmt.Println("select ssh host number must be integer")
os.Exit(1)
}
cmd := exec.Command("ssh", hosts.Hosts[key].Alias)
cmd.Stdin = os.Stdin
cmd.Stdout = os.Stdout
cmd.Stderr = os.Stderr
if err = cmd.Run(); err != nil {
fmt.Println("can't ssh connection")
panic(err)
}
}
以上です。
何かの参考になればと思います。