草庐IT

go - 初始化/管理并发 SSH 连接

coder 2024-07-11 原文

我进行了高低搜索,但我不确定我是否使用了错误的关键字,但我无法理解这一点。我正在构建一个应用程序,它接收主机名列表并通过 SSH 连接到这些主机名。它旨在维护这些连接(并在断开连接时重新连接)。定期地,我的程序将接受指令并向部分/所有这些主机执行命令。

我目前的问题是,我知道你不能初始化一个变量而不使用它,我必须为这些 SSH 连接动态创建变量,这样我就可以独立监控/管理它们(读/写,必要时重新连接等) .由于我对 go 的了解有限,而且倾向于不小心使事情过于复杂,到目前为止,我想到的最好的方法是使用一个结构并为每个连接及其参数(主机名、用户名、密码、SSH 配置详细信息、日志文件)附加位置等)。

目前我的狡猾代码看起来像这样:

package main

import (
    "os"
    "fmt"
    "flag"
    "golang.org/x/crypto/ssh"
    "io"
    "log"
    "strings"
    "time"
    "encoding/xml"
    "bufio"
    "net"
)

// SSH connection struct
type SSHConnections struct {
    Host string
    User string
    Password string
    CLILogfile string
    SSHConn ssh.Client
    SSHConfig ssh.ClientConfig
}

func main() {
    //parse list of addresses
    var ipaddr = []string{"a.b.c.d","e.f.g.h"} //hard-coded for now

    //build out SSHConnections struct

    for i := 0; i < len(ipaddr); i++ {
        tempsshConfig := &ssh.ClientConfig{
            User:   "administrator",
            Auth: []ssh.AuthMethod{
                ssh.Password("Password!"),
            },
            HostKeyCallback: ssh.InsecureIgnoreHostKey(), 
        }
        tempsshConfig.Config.Ciphers = append(tempsshConfig.Config.Ciphers, "aes128-cbc")
        var newitem = SSHConnections{
            Host: ipaddr[i],
            User: "administrator",
            Password: "Password!",
            CLILogfile: ipaddr[1],
            SSHConn: ssh.Client,
            SSHConfig: *tempsshConfig,
        }

        SSHConnections = append(SSHConnections, newitem)
    }

使用上面的代码我得到了编译错误:

type ssh.Client is not an expression
type SSHConnections is not an expression

在此之后还有特定于连接的参数(日志文件声明、额外的 SSH 参数、实际的 SSH 连接过程)我将需要管理,大概在与上面相同的结构中。上面是我目前的绊脚石,我对如何解决这个问题一无所知,更不用说将下面的单连接代码集成到上面了。

//extra SSH parameters required before connecting
sshConfig.Config.Ciphers = append(sshConfig.Config.Ciphers, "aes128-cbc")
modes := ssh.TerminalModes{
    ssh.ECHO:          0,     // disable echoing
    ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
    ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
}

//prepare logfiles
f, ferr := os.OpenFile("outputfile.txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
if ferr != nil {
    panic(ferr)
}
defer f.Close() 

//SSH connection procedure
connection, err := ssh.Dial("tcp", hostname+":22", sshConfig)
if err != nil {
    log.Fatalf("Failed to dial: %s", err)
}
session, err := connection.NewSession()
handleError(err, true, "Failed to create session: %s")
sshOut, err := session.StdoutPipe()
handleError(err, true, "Unable to setup stdin for session: %v")
sshIn, err := session.StdinPipe()
handleError(err, true, "Unable to setup stdout for session: %v")
if err := session.RequestPty("xterm", 0, 200, modes); err != nil {
    session.Close()
    handleError(err, true, "request for pseudo terminal failed: %s")
}
if err := session.Shell(); err != nil {
    session.Close()
    handleError(err, true, "request for shell failed: %s")
}

为了使我的帖子更清晰,我删除了一大堆不相关的代码。我希望我在这方面取得了远程成功。

我什至不确定结构是否是我想要的,但接收可变数量的主机名的动态性以及单独控制它们的要求是关键。稍后,我可能还需要单独断开连接,因此我正在寻找可以操纵它们的结构。

非常感谢任何帮助我指明正确方向的人。谢谢!

------------- 在此行下方更新----------------

在 mu 的下面的帖子和一些谷歌搜索之后,我的代码现在看起来像这样(删除了导入):

var connections []SSHConnections

// SSH connection struct
type SSHConnections struct {
    Host string
    User string
    Password string
    CLILogfilepath string
    CLILogfile *os.File
    SSHConn *ssh.Client
    Session *ssh.Session
    SSHOut  io.Reader
    SSHIn   io.WriteCloser
    SSHConfig *ssh.ClientConfig
    CLIReady string
    SSHConnErr error
    SessionErr  error
    SSHOutErr   error
    SSHInErr    error
    CLILogfileErr   error
    CommandQueue    chan string
}

func handleError(e error, fatal bool, customMessage ...string) {
    var errorMessage string
    if e != nil {
        if len(customMessage) > 0 {
            errorMessage = strings.Join(customMessage, " ")
        } else {
            errorMessage = "%s"
        }
        if fatal == true {
            log.Fatalf(errorMessage, e)
        } else {
            log.Print(errorMessage, e)
        }
    }
}
func writeToFile(f *os.File, err error, inputString string) {
    if _, err = f.WriteString(inputString); err != nil {
        panic(err)
    }   
}

func connectionWorker(i int) {
    //this function is to monitor SSH connections and reconnect if/when they are terminated
    fmt.Println("goroutine for: "+connections[i].Host)
    for { //not sure if this is even necessary? yet to test
        select {
            case command := <-connections[i].CommandQueue:
                fmt.Printf("received %v from queue\n", command)
            default:
                time.Sleep(1000 * time.Millisecond)
        }

    }
}

func main() {

    //parse list of addresses
    var ipaddr = []string{"host1","host2"}

    //build out SSHConnections struct
    for i := 0; i < len(ipaddr); i++ {
        tempsshConfig := &ssh.ClientConfig{
            User:   "administrator",
            Auth: []ssh.AuthMethod{
                ssh.Password("Password!"),
            },
            HostKeyCallback: ssh.InsecureIgnoreHostKey(),
        }
        tempsshConfig.Config.Ciphers = append(tempsshConfig.Config.Ciphers, "aes128-cbc")
        var newitem = SSHConnections{
            Host: ipaddr[i],
            User: "administrator",
            Password: "Password!",
            CLILogfilepath: ipaddr[i],
            SSHConfig: tempsshConfig,
        }

        connections = append(connections, newitem)
    }

    modes := ssh.TerminalModes{
        ssh.ECHO:          0,     // disable echoing
        ssh.TTY_OP_ISPEED: 14400, // input speed = 14.4kbaud
        ssh.TTY_OP_OSPEED: 14400, // output speed = 14.4kbaud
    }

    //establish connections, check each for relevant 'admin:' readiness
    for i := 0; i < len(connections); i++ {
        connections[i].SSHConn, connections[i].SSHConnErr = ssh.Dial("tcp", connections[i].Host+":22", connections[i].SSHConfig)
        if connections[i].SSHConnErr != nil {
            fmt.Println("Failed to dial: %s")
        } else {
            connections[i].Session, connections[i].SessionErr = connections[i].SSHConn.NewSession()
            handleError(connections[i].SessionErr, true, "Failed to create session: %s")
            connections[i].SSHOut, connections[i].SSHOutErr = connections[i].Session.StdoutPipe()
            handleError(connections[i].SSHOutErr, true, "Unable to setup stdin for session: %v")
            connections[i].SSHIn, connections[i].SSHInErr = connections[i].Session.StdinPipe()
            handleError(connections[i].SSHInErr, true, "Unable to setup stdout for session: %v")
            if err := connections[i].Session.RequestPty("xterm", 0, 200, modes); err != nil {
                connections[i].Session.Close()
                handleError(connections[i].SSHInErr, true, "request for pseudo terminal failed: %s")
            }
        }
        if err := connections[i].Session.Shell(); err != nil {
            connections[i].Session.Close()
            handleError(err, true, "request for shell failed: %s")
        }
        //initialise the buffered CommandQueue channel
        connections[i].CommandQueue = make(chan string, 1000)

        //prepare logfiles
        connections[i].CLILogfile, connections[i].CLILogfileErr = os.OpenFile(connections[i].CLILogfilepath+".txt", os.O_CREATE|os.O_APPEND|os.O_WRONLY, 0600)
        if connections[i].CLILogfileErr != nil {
            panic(connections[i].CLILogfileErr)
        }
        defer connections[i].CLILogfile.Close()
        writeToFile(connections[i].CLILogfile, connections[i].CLILogfileErr, "testing output to file\r\n")

        //monitor/maintain connections, monitor work queue and execute
        go connectionWorker(i)
    }
}

以上内容并不漂亮也不完美,如果有人要复制/粘贴,可能还需要进行一些调整,所以请记住这一点。我敢肯定,通过适当的测试我会偶然发现很多错误,但目前它可以按预期工作。我原来的疑问现在都得到了解答,谢谢!

最佳答案

你的循环看起来像这样:

for i := 0; i < len(ipaddr); i++ {
    // ...
    var newitem = SSHConnections{
        // ...
        SSHConn: ssh.Client,
        // ...
    }

    SSHConnections = append(SSHConnections, newitem)
}

当你构建 newitem 时,你没有 SSHConnssh.Client 所以你应该把它去掉(并且因此使用它的零值)而不是尝试使用 ssh.Client 类型作为表达式;这是你的第一个错误。

第二个错误是类似的:您使用的类型(在本例中为 SSHConnections)您确实想要使用 slice :

SSHConnections = append(SSHConnections, newitem)

应该是:

someSlice = append(someSlice, newitem)

那个循环应该看起来更像这样:

var connections []SSHConnections
for i := 0; i < len(ipaddr); i++ {
    // ...
    var newitem = SSHConnections{
        // Everything that's there now except the SSHConn ...
    }
    connections = append(connections, newitem)    
}

或者,既然您知道连接需要多大:

connections := make([]SSHConnections, len(ipaddr))
for i := 0; i < len(ipaddr); i++ {
    // ...
    var newitem = SSHConnections{
        // Everything that's there now except the SSHConn ...
    }
    connections[i] = newitem
}

然后稍后您调用 ssh.Dialconnection.NewClient 来获取可能会在您的终端中的 ssh.Client 连接

此外,您最好为 SSHConnSSConfig 使用指向 SSHConnections 的指针,因为这是包的接口(interface)想要使用的.

关于go - 初始化/管理并发 SSH 连接,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/48455865/

有关go - 初始化/管理并发 SSH 连接的更多相关文章

  1. ruby - i18n Assets 管理/翻译 UI - 2

    我正在使用i18n从头开始​​构建一个多语言网络应用程序,虽然我自己可以处理一大堆yml文件,但我说的语言(非常)有限,最终我想寻求外部帮助帮助。我想知道这里是否有人在使用UI插件/gem(与django上的django-rosetta不同)来处理多个翻译器,其中一些翻译器不愿意或无法处理存储库中的100多个文件,处理语言数据。谢谢&问候,安德拉斯(如果您已经在ruby​​onrails-talk上遇到了这个问题,我们深表歉意) 最佳答案 有一个rails3branchofthetolkgem在github上。您可以通过在Gemfi

  2. ruby-on-rails - 未初始化的常量 Psych::Syck (NameError) - 2

    在我的gem中,我需要yaml并且在我的本地计算机上运行良好。但是在将我的gem推送到ruby​​gems.org之后,当我尝试使用我的gem时,我收到一条错误消息=>"uninitializedconstantPsych::Syck(NameError)"谁能帮我解决这个问题?附言RubyVersion=>ruby1.9.2,GemVersion=>1.6.2,Bundlerversion=>1.0.15 最佳答案 经过几个小时的研究,我发现=>“YAML使用未维护的Syck库,而Psych使用现代的LibYAML”因此,为了解决

  3. ruby - 续集在添加关联时访问many_to_many连接表 - 2

    我正在使用Sequel构建一个愿望list系统。我有一个wishlists和itemstable和一个items_wishlists连接表(该名称是续集选择的名称)。items_wishlists表还有一个用于facebookid的额外列(因此我可以存储opengraph操作),这是一个NOTNULL列。我还有Wishlist和Item具有续集many_to_many关联的模型已建立。Wishlist类也有:selectmany_to_many关联的选项设置为select:[:items.*,:items_wishlists__facebook_action_id].有没有一种方法可以

  4. ruby - Capistrano 3 在任务中更改 ssh_options - 2

    我尝试使用不同的ssh_options在同一阶段运行capistranov.3任务。我的production.rb说:set:stage,:productionset:user,'deploy'set:ssh_options,{user:'deploy'}通过此配置,capistrano与用户deploy连接,这对于其余的任务是正确的。但是我需要将它连接到服务器中配置良好的an_other_user以完成一项特定任务。然后我的食谱说:...taskswithoriginaluser...task:my_task_with_an_other_userdoset:user,'an_othe

  5. ruby-on-rails - 未在 Ruby 中初始化的对象 - 2

    我在Rails工作并有以下类(class):classPlayer当我运行时bundleexecrailsconsole然后尝试:a=Player.new("me",5.0,"UCLA")我回来了:=>#我不知道为什么Player对象不会在这里初始化。关于可能导致此问题的操作/解释的任何建议?谢谢,马里奥格 最佳答案 havenoideawhythePlayerobjectwouldn'tbeinitializedhere它没有初始化很简单,因为你还没有初始化它!您已经覆盖了ActiveRecord::Base初始化方法,但您没有调

  6. ruby - 无法在 60 秒内获得稳定的 Firefox 连接 (127.0.0.1 :7055) - 2

    我使用的是Firefox版本36.0.1和Selenium-Webdrivergem版本2.45.0。我能够创建Firefox实例,但无法使用脚本继续进行进一步的操作无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055)错误。有人能帮帮我吗? 最佳答案 我遇到了同样的问题。降级到firefoxv33后一切正常。您可以找到旧版本here 关于ruby-无法在60秒内获得稳定的Firefox连接(127.0.0.1:7055),我们在StackOverflow上找到一个类

  7. ruby-on-rails - ActionController::RoutingError: 未初始化常量 Api::V1::ApiController - 2

    我有用于控制用户任务的Rails5API项目,我有以下错误,但并非总是针对相同的Controller和路由。ActionController::RoutingError:uninitializedconstantApi::V1::ApiController我向您描述了一些我的项目,以更详细地解释错误。应用结构路线scopemodule:'api'donamespace:v1do#=>Loginroutesscopemodule:'login'domatch'login',to:'sessions#login',as:'login',via::postend#=>Teamroutessc

  8. ruby-on-rails - 获取 inf-ruby 以使用 ruby​​ 版本管理器 (rvm) - 2

    我安装了ruby​​版本管理器,并将RVM安装的ruby​​实现设置为默认值,这样'哪个ruby'显示'~/.rvm/ruby-1.8.6-p383/bin/ruby'但是当我在emacs中打开inf-ruby缓冲区时,它使用安装在/usr/bin中的ruby​​。有没有办法让emacs像shell一样尊重ruby​​的路径?谢谢! 最佳答案 我创建了一个emacs扩展来将rvm集成到emacs中。如果您有兴趣,可以在这里获取:http://github.com/senny/rvm.el

  9. ruby - 这两个 Ruby 类初始化定义有什么区别? - 2

    我正在阅读一本关于Ruby的书,作者在编写类初始化定义时使用的形式与他在本书前几节中使用的形式略有不同。它看起来像这样:classTicketattr_accessor:venue,:datedefinitialize(venue,date)self.venue=venueself.date=dateendend在本书的前几节中,它的定义如下:classTicketattr_accessor:venue,:datedefinitialize(venue,date)@venue=venue@date=dateendend在第一个示例中使用setter方法与在第二个示例中使用实例变量之间是

  10. ruby-on-rails - 事件管理员日期过滤器日期格式自定义 - 2

    是否有简单的方法来更改默认ISO格式(yyyy-mm-dd)的ActiveAdmin日期过滤器显示格式? 最佳答案 您可以像这样为日期选择器提供额外的选项,而不是覆盖js:=f.input:my_date,as::datepicker,datepicker_options:{dateFormat:"mm/dd/yy"} 关于ruby-on-rails-事件管理员日期过滤器日期格式自定义,我们在StackOverflow上找到一个类似的问题: https://s

随机推荐