New module: SSH client
i think ssh support is a must for devops, it is possible for risor?
thank you for your amazing work :)
Hi @runsisi, probably so. I would guess you're most interested in having Risor be an SSH client, right?
Looks like we should just be able to wrap golang.org/x/crypto/ssh.
Any chance you want to take a stab at the implementation? I could provide guidance as needed :-)
I can look into this, if no one else is, @runsisi do you have any specific use cases in mind?
hi @myzie i'm so sorry for delayed response, really busy these days :(
hi @luisdavim , my use case is simple enough:
- connect to remote node
- scp local file/directory to remote node (for directory copy then something like https://github.com/golang/go/issues/62484 ?)
- run command on remote node and get stdout/stderr output (maybe in real time)
- with context support, e.g., cancel/timeout, would be better :)
something like the code snippet:
scp
import "github.com/bramvdbogaerde/go-scp"
func ScpFile(ctx context.Context, sshc *ssh.Client, r io.Reader, p string, perm os.FileMode) error {
scpc, err := scp.NewClientBySSH(sshc)
if err != nil {
return errors.New(err)
}
defer scpc.Close()
if err := scpc.CopyFile(ctx, r, p, "0"+strconv.FormatUint(uint64(perm), 8)); err != nil {
return errors.New(err)
}
return nil
}
func ScpContent(ctx context.Context, sshc *ssh.Client, r io.Reader, p string, perm os.FileMode, len int) error {
scpc, err := scp.NewClientBySSH(sshc)
if err != nil {
return errors.New(err)
}
defer scpc.Close()
if err := scpc.Copy(ctx, r, p, "0"+strconv.FormatUint(uint64(perm), 8), int64(len)); err != nil {
return errors.New(err)
}
return nil
}
run command
func SshRunCommand(sshc *ssh.Client, cmdline string, out io.Writer) (string, string, error) {
sess, err := sshc.NewSession()
if err != nil {
return "", "", errors.New(err)
}
defer sess.Close()
if out == nil {
out = io.Discard
}
sob := &bytes.Buffer{}
seb := &bytes.Buffer{}
var sow io.Writer = sob
var sew io.Writer = seb
if out != nil {
sw := &singleWriter{
w: out,
}
sow = io.MultiWriter(sob, sw)
sew = io.MultiWriter(seb, sw)
}
sess.Stdout = sow
sess.Stderr = sew
if err := sess.Start(cmdline); err != nil {
return "", "", errors.New(err)
}
if err := sess.Wait(); err != nil {
return "", "", errors.New(err)
}
return strings.TrimSpace(sob.String()), strings.TrimSpace(sob.String()), nil
}
Hi! I am using Risor for myself and would like to work on this issue.
Hi @roopeshsn, to my knowledge no one has started on this yet. Go ahead and take a stab at it and I'm sure a couple of us will help with reviews.
It seems like the main two cases are 1) an scp like command and 2) running a command remotely via SSH.
Hi @roopeshsn, to my knowledge no one has started on this yet. Go ahead and take a stab at it and I'm sure a couple of us will help with reviews.
It seems like the main two cases are 1) an
scplike command and 2) running a command remotely via SSH.
Thanks! I'll take a look at the code and reach back for queries.
Hi @myzie! For SCP, there is a package called go-scp. It depends on crypto/ssh for copying files. From the discussions, what I understood was that in addition to implementing SCP, we need the crypto/ssh package to be implemented in Risor for other tasks, right?
It's ok to depend on another library. When we do that, it needs to be a Risor module that has its own go.mod file. For example this would make it like the K8s module.
https://github.com/risor-io/risor/blob/main/modules/kubernetes/go.mod