Mon code est censé SSH sur un hôte distant (disons Routeurs) et exécuter plusieurs commandes sur l'hôte distant et renvoyer les sorties.
Le code ci-joint est simplifié et comporte trois parties:
Main
: lit la liste des commandes puis, en utilisant la fonction ExecCommands
, compose / ssh vers un hôte distant pour exécuter les commandes.ExecCommands
fonction ExecCommands
prend l'adresse IP de l'hôte distant, la liste des commandes et SSH ClientConfig
utilisé pour SSH. Ensuite, il se connecte à l'adresse IP et exécute les commandes une par une. À la fin, renvoie la sortie de toutes les commandes dans une seule chaîneInsecureClientConfig
qui ne fait en réalité pas grand-chose sauf la création d'un SSH ClientConfig
qui est utilisé pour la fonction ExecCommands
Ce programme fonctionne bien lorsque je veux simplement appliquer certaines commandes ou config et enregistrer le résultat complet. Je veux dire ExecCommands
prend le tas de commandes, les pousse toutes vers l'hôte distant et renvoie (ou enregistre) toute la sortie des commandes appliquées dans une chaîne en tant que sortie.
Problème:
Objectif:
ExecCommands
fonction ExecCommands
de manière à ce qu'elle fournisse une sortie distincte pour chaque commande qu'elle applique. Cela signifie que si pour remote-host#1
il applique 10 commandes, je devrais avoir 10 chaînes distinctes en sortie.Conditions / Restrictions:
Run
, Shell
, Output
, Start
dans le package SSHPoints:
ExecCommands
. J'ai mis une version simplifiée de l'ensemble du code pour donner une idéestdout, err := session.StdoutPipe()
pour exécuter plusieurs commandes, ce qui signifie -as pipe - il est seulement possible de lire Reader lorsque le travail est terminé.Session.Stdout
et Session.Stdin
intérieur de la boucle for dans la fonction ExecCommands
. Essayé mais sans succès.Code:
package main import ( "errors" "fmt" "io/ioutil" "log" "time" "golang.org/x/crypto/ssh" ) func main() { // List of the commands should be sent to the devices listCMDs := []string{ "set cli op-command-xml-output on", "test routing fib-lookup virtual-router default ip 1.1.1.1", "test routing fib-lookup virtual-router default ip 2.2.2.2", "show interface ethernet1/1", "show interface ethernet1/2", "test security-policy-match protocol 6 source 1.1.1.1 destination 2.2.2.2 destination-port 443 from ZONE1 to ZONE2", "test security-policy-match protocol 6 source 10.0.0.1 destination 10.0.2.1 destination-port 443 from ZONE1 to ZONE2", "exit", } sshconfig := InsecureClientConfig("admin", "admin") s, err := ExecCommands("192.168.1.250", listCMDs, sshconfig) fmt.Println(s, err) } // ExecCommands ... func ExecCommands(ipAddr string, commands []string, sshconfig *ssh.ClientConfig) (string, error) { // Gets IP, credentials and config/commands, SSH Config (Timeout, Ciphers, ...) and returns // output of the device as "string" and an error. If error == nil, means program was able to SSH with no issue // Creating outerr as Output Error. outerr := errors.New("nil") outerr = nil // Creating Output as String var outputStr string // Dial to the remote-host client, err := ssh.Dial("tcp", ipAddr+":22", sshconfig) if err != nil { log.Fatal(err) } defer client.Close() // Create sesssion session, err := client.NewSession() if err != nil { log.Fatal(err) } defer session.Close() // StdinPipee() returns a pipe that will be connected to the remote command's standard input when the command starts. // StdoutPipe() returns a pipe that will be connected to the remote command's standard output when the command starts. stdin, err := session.StdinPipe() if err != nil { log.Fatal(err) } stdout, err := session.StdoutPipe() if err != nil { log.Fatal(err) } // Start remote shell err = session.Shell() if err != nil { log.Fatal(err) } // Send the commands to the remotehost one by one. for _, cmd := range commands { _, err := stdin.Write([]byte(cmd + "\n")) if err != nil { log.Fatal(err) } } // Wait for session to finish err = session.Wait() if err != nil { log.Fatal(err) } strByte, _ := ioutil.ReadAll(stdout) outputStr = string(strByte) return outputStr, outerr } // InsecureClientConfig ... func InsecureClientConfig(userStr, passStr string) *ssh.ClientConfig { SSHconfig := &ssh.ClientConfig{ User: userStr, Timeout: 5 * time.Second, Auth: []ssh.AuthMethod{ssh.Password(passStr)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Config: ssh.Config{ Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-cbc", "aes192-cbc", "aes256-cbc", "3des-cbc", "des-cbc"}, KeyExchanges: []string{"diffie-hellman-group1-sha1", "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1"}, }, } return SSHconfig }
3 Réponses :
Vérifiez ceci: https://github.com/yahoo/vssh Vous pouvez définir les sessions sur le nombre de commandes à exécuter simultanément, puis envoyer chaque commande à l'hôte distant via la méthode d'exécution et obtenir le résultat individuellement!
Merci @Mehrdad pour cela. Êtes-vous sûr qu'il prend en charge plusieurs commandes par session? Dans la documentation, la méthode run
ressemble à une seule commande?
il crée une connexion puis pour chaque exécution, il crée une session. (vous devez définir le nombre maximum de sessions)
Merci. essaiera de vous faire savoir quel est le résultat
Cela fonctionne correctement:
package main import ( "bufio" "errors" "fmt" "log" "time" "golang.org/x/crypto/ssh" ) func main() { // List of the commands should be sent to the devices listCMDs := []string{ "set cli op-command-xml-output on\n", "test routing fib-lookup virtual-router default ip 1.1.1.1\n", "test routing fib-lookup virtual-router default ip 2.2.2.2\n", "show interface ethernet1/1\n", "show interface ethernet1/2\n", "test security-policy-match protocol 6 source 1.1.1.1 destination 2.2.2.2 destination-port 443 from ZONE1 to ZONE2\n", "test security-policy-match protocol 6 source 10.0.0.1 destination 10.0.2.1 destination-port 443 from ZONE1 to ZONE2\n", "exit", } sshconfig := InsecureClientConfig("admin", "Ghazanfar1!") s, _ := ExecCommands("192.168.1.249", listCMDs, sshconfig) for _, item := range s { fmt.Println(item) fmt.Println("-------------------------------") } } // ExecCommands ... func ExecCommands(ipAddr string, commands []string, sshconfig *ssh.ClientConfig) ([]string, error) { // Gets IP, credentials and config/commands, SSH Config (Timeout, Ciphers, ...) and returns // output of the device as "string" and an error. If error == nil, means program was able to SSH with no issue // Creating outerr as Output Error. outerr := errors.New("nil") outerr = nil // Creating Output as String var outputStr []string var strTmp string // Dial to the remote-host client, err := ssh.Dial("tcp", ipAddr+":22", sshconfig) if err != nil { log.Fatal(err) } defer client.Close() // Create sesssion session, err := client.NewSession() if err != nil { log.Fatal(err) } defer session.Close() // StdinPipee() returns a pipe that will be connected to the remote command's standard input when the command starts. // StdoutPipe() returns a pipe that will be connected to the remote command's standard output when the command starts. stdin, err := session.StdinPipe() if err != nil { log.Fatal(err) } stdout, err := session.StdoutPipe() if err != nil { log.Fatal(err) } // Start remote shell err = session.Shell() if err != nil { log.Fatal(err) } stdinLines := make(chan string) go func() { scanner := bufio.NewScanner(stdout) for scanner.Scan() { stdinLines <- scanner.Text() } if err := scanner.Err(); err != nil { log.Printf("scanner failed: %v", err) } close(stdinLines) }() // Send the commands to the remotehost one by one. for i, cmd := range commands { _, err := stdin.Write([]byte(cmd + "\n")) if err != nil { log.Fatal(err) } if i == len(commands)-1 { _ = stdin.Close() // send eof } // wait for command to complete // we'll assume the moment we've gone 1 secs w/o any output that our command is done timer := time.NewTimer(0) InputLoop: for { timer.Reset(time.Second) select { case line, ok := <-stdinLines: if !ok { log.Println("Finished processing") break InputLoop } strTmp += line strTmp += "\n" case <-timer.C: break InputLoop } } outputStr = append(outputStr, strTmp) //log.Printf("Finished processing %v\n", cmd) strTmp = "" } // Wait for session to finish err = session.Wait() if err != nil { log.Fatal(err) } return outputStr, outerr } // InsecureClientConfig ... func InsecureClientConfig(userStr, passStr string) *ssh.ClientConfig { SSHconfig := &ssh.ClientConfig{ User: userStr, Timeout: 5 * time.Second, Auth: []ssh.AuthMethod{ssh.Password(passStr)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Config: ssh.Config{ Ciphers: []string{"aes128-ctr", "aes192-ctr", "aes256-ctr", "aes128-cbc", "aes192-cbc", "aes256-cbc", "3des-cbc", "des-cbc"}, KeyExchanges: []string{"diffie-hellman-group1-sha1", "diffie-hellman-group-exchange-sha1", "diffie-hellman-group14-sha1"}, }, } return SSHconfig }
Puisque vous avez un nombre limité de commandes à exécuter sur des matériels spéciaux et que vous connaissez le modèle de sortie de chaque commande, vous pouvez utiliser des strings.Split
ou regexp
pour diviser la sortie.
Et si vous n'avez pas de commande echo
, mais que vous connaissez une commande avec une réponse rapide avec un modèle de sortie unique, vous pouvez la remplacer par la commande echo
dans l'exemple suivant (numéro 2).
Depuis une session accepte un seul appel à Run
, Start
, Shell
, Output
ou CombinedOutput
, et vous ne voulez pas commencer une nouvelle session par commande:
La clé est d'utiliser un strings.Builder
et de le vider en utilisant sb.Reset()
avant d'envoyer la commande, et en utilisant io.Copy
pour copier simultanément le stdout de la session dans strings.Builder
(en supposant que vous n'avez pas besoin de stderr de session):
sb := new(strings.Builder) go io.Copy(sb, stdout) commands := []string{"uname -a", "sleep 1", "pwd", "whoami"} delim := "********--------========12345678" for _, cmd := range commands { _, err = stdin.Write([]byte("echo " + delim + "\n")) if err != nil { log.Fatal(err) } _, err := stdin.Write([]byte(cmd + "\n")) if err != nil { log.Fatal(err) } } _, err = stdin.Write([]byte("exit\n")) if err != nil { log.Fatal(err) } err = session.Wait() // Wait for session to exit if err != nil { log.Fatal(err) } ans := strings.Split(sb.String(), delim) ans = ans[1:] // remove ssh greetings
sb := new(strings.Builder) go io.Copy(sb, stdout) commands := []string{"uname -a", "sleep 1", "pwd", "whoami", "exit"} wait := []time.Duration{10, 1200, 20, 10, 10} // * time.Millisecond ans := []string{} time.Sleep(10 * time.Millisecond) // wait for the ssh greetings // Send the commands to the remotehost one by one. for i, cmd := range commands { sb.Reset() fmt.Println("*** command:\t", cmd) _, err := stdin.Write([]byte(cmd + "\n")) if err != nil { log.Fatal(err) } time.Sleep(wait[i] * time.Millisecond) // wait for the command to finish s := sb.String() fmt.Println("*** response:\t", s) ans = append(ans, s) }
strings.Split
(Remarque: vous pouvez remplacer echo
par n'importe quelle commande rapide avec un modèle de sortie connu):sb := new(strings.Builder) go io.Copy(sb, stdout)
IIRC, une nouvelle session n'aura pas besoin de réauthentification. Seul le cadran nécessite une authentification.
Si vous devez les exécuter dans un seul shell, vous êtes limité par ce que ce shell peut faire. Il n'y a rien que ssh ou Go puisse faire pour corriger la sortie de ce shell distant. Pouvez-vous faire écho à un délimiteur pour le flux de sortie séparé la sortie de chaque commande?
@Burak Serdar. Merci pour la réponse rapide. cela vous dérangerait-il de me donner un exemple? La dernière fois que j'ai essayé n'a pas réussi
@JimB. Merci à vous aussi pour la réponse rapide. Je n'ai pas suivi exactement ce que tu veux dire
Une session est censée exécuter une commande. Vous pouvez créer une session, exécuter une commande, obtenir la réponse et créer une autre session pour la suivante. Quelle erreur avez-vous obtenu? Le seul exemple que je peux fournir est dans ce projet que j'ai écrit il y a quelque temps, qui fait en fait ce dont vous avez besoin: ssh dans de nombreux hôtes et faire des choses dessus: github.com/bserdar/watermelon/blob/master/server/backends / ... (c'est assez gros, mais cela crée une connexion et plusieurs sessions)
@Burak Serdar C'est ce que je comprends:
session1, err := client.NewSession()
puissession1.Shell()
puissession1.Output("COMMAND")
enfinsession1.Wait()
Et même pour session2, session3 Si c'est le cas, après l'exécution desession1.Output
je reçoisStdout already set
ceStdout already set
Aussi j'ai juste essayé ceci:
session1, err := client.NewSession()
puissession1.Run("COMMAND")
et enregistrer la sortie dans un Buffervar b1 bytes.Buffer
etsession1.Stdout = &b1
Et même pour session2, session3 mais pas de chance :(@Deibedyn: quelle partie ne comprenez-vous pas? Vous interagissez avec un seul shell et n'avez donc qu'un seul flux de sortie. Si vous avez besoin de séparer la sortie de chaque commande, pouvez-vous délimiter la sortie de l'intérieur du shell?
@JimB Merci d'avoir essayé de vous aider. Compris. Pas sûr que je puisse mettre n'importe quel caractère ou quoi que ce soit d'autre pour délimiter la sortie. Le fait est que de nombreuses commandes ont une sortie similaire (quel processus est en cours d'exécution, quelle est la route vers cette adresse IP, etc.) J'ai essayé d'utiliser une session différente [ play.golang.org/p/HYbLcrAlNz6] et ici [ play.golang.org/ p / c3anBjdKM9c] en utilisant la commande
Run
etOutput
. Pas de chance@ mh-cbon Merci pour votre commentaire. Le fait est que presque aucun des périphériques réseau - comme illustré dans ma question, ne prend pas en charge l'écho