diff --git a/cmd/climc/shell/hosts.go b/cmd/climc/shell/hosts.go index b9def8987e..470fe53022 100644 --- a/cmd/climc/shell/hosts.go +++ b/cmd/climc/shell/hosts.go @@ -18,11 +18,13 @@ import ( "fmt" "yunion.io/x/jsonutils" + "yunion.io/x/log" "yunion.io/x/onecloud/pkg/mcclient" "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/ssh" ) func init() { @@ -664,4 +666,44 @@ func init() { printObject(result) return nil }) + + type HostSSHLoginOptions struct { + ID string `help:"ID or name of host"` + Port int `help:"SSH service port" default:"22"` + } + R(&HostSSHLoginOptions{}, "host-ssh", "SSH login of a host", func(s *mcclient.ClientSession, args *HostSSHLoginOptions) error { + srvid, e := modules.Hosts.GetId(s, args.ID, nil) + if e != nil { + return e + } + i, e := modules.Hosts.GetLoginInfo(s, srvid, nil) + if e != nil { + return e + } + host, err := i.GetString("ip") + if err != nil { + return err + } + user, err := i.GetString("username") + if err != nil { + return err + } + passwd, err := i.GetString("password") + if err != nil { + return err + } + port := 22 + if args.Port != 22 { + port = args.Port + } + sshCli, err := ssh.NewClient(host, port, user, passwd, "") + if err != nil { + return err + } + log.Infof("ssh %s:%d", host, port) + if err := sshCli.RunTerminal(); err != nil { + return err + } + return nil + }) } diff --git a/cmd/climc/shell/servers.go b/cmd/climc/shell/servers.go index de8ae21587..608325b928 100644 --- a/cmd/climc/shell/servers.go +++ b/cmd/climc/shell/servers.go @@ -34,6 +34,7 @@ import ( "yunion.io/x/onecloud/pkg/mcclient/modules" "yunion.io/x/onecloud/pkg/mcclient/options" "yunion.io/x/onecloud/pkg/util/fileutils2" + "yunion.io/x/onecloud/pkg/util/ssh" ) func init() { @@ -1218,4 +1219,77 @@ func init() { } return nil }) + + R(&options.ServerSSHLoginOptions{}, "server-ssh", "Use SSH login a server", func(s *mcclient.ClientSession, opts *options.ServerSSHLoginOptions) error { + srv, err := modules.Servers.Get(s, opts.ID, nil) + if err != nil { + return err + } + + srvid, err := srv.GetString("id") + if err != nil { + return err + } + + address := make([]string, 0) + nics, err := srv.GetArray("nics") + if err != nil { + return err + } + for _, nic := range nics { + if addr, err := nic.GetString("ip_addr"); err == nil { + address = append(address, addr) + } + } + if len(address) == 0 { + return fmt.Errorf("Not found ip address from server %s", opts.ID) + } + + params := jsonutils.NewDict() + if len(opts.Key) > 0 { + privateKey, e := ioutil.ReadFile(opts.Key) + if e != nil { + return e + } + params.Add(jsonutils.NewString(string(privateKey)), "private_key") + } + + i, e := modules.Servers.GetLoginInfo(s, srvid, params) + if e != nil { + return e + } + passwd, err := i.GetString("password") + if err != nil { + return err + } + if opts.Password != "" { + passwd = opts.Password + } + user, err := i.GetString("username") + if err != nil { + return err + } + if opts.User != "" { + user = opts.User + } + + host := address[0] + if opts.Host != "" { + host = opts.Host + } + port := 22 + if opts.Port != 22 { + port = opts.Port + } + + sshCli, err := ssh.NewClient(host, port, user, passwd, "") + if err != nil { + return err + } + log.Infof("ssh %s:%d", host, port) + if err := sshCli.RunTerminal(); err != nil { + return err + } + return nil + }) } diff --git a/pkg/mcclient/options/servers.go b/pkg/mcclient/options/servers.go index 70510833ca..ca7d1f8c8f 100644 --- a/pkg/mcclient/options/servers.go +++ b/pkg/mcclient/options/servers.go @@ -68,6 +68,14 @@ type ServerLoginInfoOptions struct { Key string `help:"File name of private key, if password is encrypted by key"` } +type ServerSSHLoginOptions struct { + ServerLoginInfoOptions + Host string `help:"IP address or hostname of the server"` + Port int `help:"SSH service port" default:"22"` + User string `help:"SSH login user"` + Password string `help:"SSH password"` +} + type ServerIdsOptions struct { ID []string `help:"ID of servers to operate" metavar:"SERVER" json:"-"` } diff --git a/pkg/util/ssh/ssh.go b/pkg/util/ssh/ssh.go index d4967b7d76..438478030d 100644 --- a/pkg/util/ssh/ssh.go +++ b/pkg/util/ssh/ssh.go @@ -18,10 +18,12 @@ import ( "bytes" "fmt" "io" + "os" "strings" "time" "golang.org/x/crypto/ssh" + "golang.org/x/crypto/ssh/terminal" "yunion.io/x/log" "yunion.io/x/pkg/errors" @@ -168,3 +170,57 @@ func ParseOutput(output []byte) []string { func (s *Client) Close() { s.client.Close() } + +func (s *Client) RunTerminal() error { + defer s.Close() + session, err := s.client.NewSession() + if err != nil { + return errors.Wrap(err, "open new session") + } + defer session.Close() + + fd := int(os.Stdin.Fd()) + state, err := terminal.MakeRaw(fd) + if err != nil { + return errors.Wrap(err, "make raw terminal") + } + defer terminal.Restore(fd, state) + + w, h, err := terminal.GetSize(fd) + if err != nil { + return errors.Wrap(err, "get terminal size") + } + + modes := ssh.TerminalModes{ + ssh.ECHO: 1, + ssh.TTY_OP_ISPEED: 14400, + ssh.TTY_OP_OSPEED: 14400, + } + + term := os.Getenv("TERM") + if term == "" { + term = "xterm-256color" + } + if err := session.RequestPty(term, h, w, modes); err != nil { + return errors.Wrap(err, "session xterm") + } + + session.Stdout = os.Stdout + session.Stderr = os.Stderr + session.Stdin = os.Stdin + + if err := session.Shell(); err != nil { + return errors.Wrap(err, "session shell") + } + + if err := session.Wait(); err != nil { + if e, ok := err.(*ssh.ExitError); ok { + switch e.ExitStatus() { + case 130: + return nil + } + } + return errors.Wrap(err, "ssh wait") + } + return nil +}