Golang : Execute terminal command to remote machine example
Problem:
You need to query a remote machine with ssh
to extract information such as memory usage or CPU information. Instead of opening a terminal and ssh to each individual remote machine, you want to write a Golang program that will execute a command and parse the output to your terminal or save to text files for processing.
How to do that?
Solution:
This is similar to Python's paramiko
module that allow developers to create a remote session to a Unix/Linux machine. For Golang, we will use golang.org/x/crypto/ssh
and golang.org/x/crypto/ssh/terminal
packages to achieve similar result. In the code example below, we will connect to a remote Linux machine and execute the command cat /proc/cpuinfo
to extract the CPU information. It can return static response from commands such as ps -ef
or whoami
, but not top
command.
NOTE: If you're planning to query specific command only. Feel free to modify and hard code the command into the program instead of asking username/password input from the user. Make sure that the program has proper ownership and use proper mechanism to hide the plain password hard coded into the program.
Here you go!
package main
import (
"bufio"
"bytes"
"fmt"
"golang.org/x/crypto/ssh"
"golang.org/x/crypto/ssh/terminal"
"net"
"os"
"strings"
)
func GetCredential() (string, string) {
reader := bufio.NewReader(os.Stdin)
fmt.Print("Enter Username: ")
username, _ := reader.ReadString('\n')
fmt.Print("Enter Password: ")
bytePassword, err := terminal.ReadPassword(0)
if err != nil {
panic(err)
}
password := string(bytePassword)
return strings.TrimSpace(username), strings.TrimSpace(password)
}
func main() {
if len(os.Args) != 3 {
fmt.Printf("Usage : %s <hostname> <port> \n", os.Args[0])
os.Exit(0)
}
hostname := os.Args[1]
port := os.Args[2]
username, password := GetCredential()
config := &ssh.ClientConfig{
User: username,
Auth: []ssh.AuthMethod{ssh.Password(password)},
HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error {
return nil
},
}
fmt.Println("\nConnecting to ", hostname, port)
hostaddress := strings.Join([]string{hostname, port}, ":")
client, err := ssh.Dial("tcp", hostaddress, config)
if err != nil {
panic(err.Error())
}
for {
session, err := client.NewSession()
if err != nil {
panic(err.Error())
}
defer session.Close()
fmt.Println("To exit this program, hit Control-C")
fmt.Printf("Enter command to execute on %s : ", hostname)
// fmt.Scanf is unable to accept command with parameters
// see solution at
// https://www.socketloop.com/tutorials/golang-accept-input-from-user-with-fmt-scanf-skipped-white-spaces-and-how-to-fix-it
//fmt.Scanf("%s", &cmd)
commandReader := bufio.NewReader(os.Stdin)
cmd, _ := commandReader.ReadString('\n')
//log.Printf(cmd)
fmt.Println("Executing command ", cmd)
// capture standard output
// will NOT be able to handle refreshing output such as TOP command
// executing top command will result in panic
var buff bytes.Buffer
session.Stdout = &buff
if err := session.Run(cmd); err != nil {
panic(err.Error())
}
fmt.Println(buff.String())
}
}
Sample output after cat /proc/cpuinfo
on a remote Linux machine:
./remoteQuery 1xx.xxx.xx.xx xxxx
Enter Username: xxxxxxx
Enter Password:
Connecting to 1xx.xxx.xx.xx xxxx
To exit this program, hit Control-C
Enter command to execute on 1xx.xxx.xx.xx : cat /proc/cpuinfo
Executing command cat /proc/cpuinfo
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model : 62
model name : Intel(R) Xeon(R) CPU E5-2630L v2 @ 2.40GHz
stepping : 4
microcode : 0x1
cpu MHz : 2399.998
cache size : 15360 KB
physical id : 0
siblings : 1
core id : 0
cpu cores : 1
apicid : 0
initial apicid : 0
fpu : yes
fpu_exception : yes
cpuid level : 13
wp : yes
flags : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca cmov pat pse36 clflush mmx fxsr sse sse2 ss syscall nx pdpe1gb rdtscp lm constant_tsc arch_perfmon rep_good nopl pni pclmulqdq vmx ssse3 cx16 pcid sse4_1 sse4_2 x2apic popcnt tsc_deadline_timer aes xsave avx f16c rdrand hypervisor lahf_lm vnmi ept fsgsbase tsc_adjust smep erms xsaveopt arat
bugs :
bogomips : 4799.99
clflush size : 64
cache_alignment : 64
address sizes : 40 bits physical, 48 bits virtual
power management:
Happy coding!
References:
https://github.com/Juniper/go-netconf/issues/27
https://stackoverflow.com/questions/23019890/golang-write-input-and-get-output-from-terminal-process
See also : Golang : How to check if your program is running in a terminal
By Adam Ng
IF you gain some knowledge or the information here solved your programming problem. Please consider donating to the less fortunate or some charities that you like. Apart from donation, planting trees, volunteering or reducing your carbon footprint will be great too.
Advertisement
Tutorials
+7.9k Golang : Number guessing game with user input verification example
+14.1k Golang : Find network of an IP address
+11.6k Golang : Convert a rune to unicode style string \u
+7.8k Golang : Add build version and other information in executables
+8.5k Golang : How to capture return values from goroutines?
+5.7k Golang : Build new URL for named or registered route with Gorilla webtoolkit example
+4.8k Swift : Convert (cast) Float to Int or Int32 value
+37.3k Golang : Comparing date or timestamp
+21.4k Golang : Join arrays or slices example
+19.4k Golang : Append content to a file
+5.1k Golang : Intercept, inject and replay HTTP traffics from web server
+4.8k Golang : Customize scanner.Scanner to treat dash as part of identifier