Search This Blog

Showing posts with label ssh. Show all posts
Showing posts with label ssh. Show all posts

Thursday, October 10, 2019

Executing a remote SSH command in background

Whenever you execute a SSH command on a remote host it will wait until that process ends. Even with Unix targets if you add a "&" on the end of the command and/or add "nohup" on the beginning you will still wait. Same obviously applies in OpenAF:

var aTarget = { 
    host : "some.host",
    port : 22,
    login: "me",
    pass : "change"
    //id: "~/.ssh/mykey_rsa"
}

var res = $ssh(aTarget)
.sh("nohup wget https://download.dokuwiki.org/src/dokuwiki/dokuwiki-stable.tgz &")
.get();

// res will have all the output from wget as if '&' was never there in the first place

The reason is related to how SSH works. While any of the stdout, stderr or stdin are "open" the execution command won't end. So a simple way to solve it is to change the execution command to:

var res = $ssh(aTarget)
.sh("nohup wget https://download.dokuwiki.org/src/dokuwiki/dokuwiki-stable.tgz 2> wget.out > /dev/null < /dev/null &")
.get();

// res.stdout and res.stderr now don't shown anything since the process was
// left running on the server

Later you can check the execution command output:

var res = $ssh(aTarget).sh("cat wget.out").get(0).stdout;

What about promises?

Yes, you could code something like:

var res;
var promise = $do(() => {
    res = $ssh(aTarget)
          .sh("wget https://download.dokuwiki.org/src/dokuwiki/dokuwiki-stable.tgz")
          .get();
});

But if you are running on a cloud function (e.g. AWS Lambda) payed by execution time you don't want to wait for the promise to be fullfilled (specially if the download will or might take some time).

Sunday, August 11, 2019

Executing commands remotely via SSH

All the functionality to access SSH & SFTP is encapsulated within the OpenAF's SSH plugin. You can use it directly but it might be hard to keep up with methods and function parameters. On oJob definitions it's easy using the oJob-common functionality. Directly on OpenAF code to make it easy to use and read there is a shortcut: $ssh

The $ssh is a function that receives the details on how to access a host via SSH as a map. The returned object can then be used to chain command to be executed remotely and finally execute them returning an array of maps with the executions result. Let's check a simple example:
$ssh({
   host: "some.host",
   login: "aUser",
   pass: "aPass"
})
.sh("id")
.get();
/*[
  {
    "stdout": "uid=12345(user) gid=500(users) groups=500(users)",
    "stderr": "",
    "exitcode": 0,
    "cmd": "id"
  }
]*/

Why is the get function returning an array? Can a private key be used? What if the SSH server has  different port? How do I provide stdin contents? Let's take it into parts.

Connection options

In the previous example we used host, login and pass but there are more possible options. Some of them have default values like port = 22. Here is the list (you can also check it out in openaf-console executing "help $ssh.get"):

  • host - The SSH host address
  • port - The SSH port (defaults to 22)
  • login - The login username (encrypted or not)
  • id - The filepath to a SSH identity key file
  • pass - The password of the login or the identity key
  • timeout - The connection timeout in ms
  • compress - A boolean determining if the connection should be compressed or not
It's also possible to write all these options in a single url:

ssh://[login]:[pass]@[host]:[port]/[id]?timeout=[timeout]&compression=[compression]


The execution function

The main execution function is "sh". This function receives a command as the first argument and a stdin as a second argument. They can be chained to a reasonable amount of calls that will be executed sequentially:
$ssh("ssh://aUser:aPass@somehost")
.sh("cd /some/dir && ./start.sh")
.sh("perl", "print(123)")
.get();
Note: despite being able to chain execution commands they are still "independent". Check in the example how you can change directory and then execute a command.

Retrieve results

To execute the chained commands there are two options: "get" and "exec". "get" will pack all the executions into an array of maps, each map with the corresponding command execution stdout, stderr and exitcode.  But the resulting map is only returned upon the end of the commands chain execution. Using ".exec" will still return the map on the end of the execution but any stdout and stderr output will be redirected to the scripts stdout and stderr.

If you have several $ssh entries in your script executing remote commands that return a lot of lines you might need "line labels" to understand them. This can easily be achieve using the ".cb" function (callback):
> ow.loadFormat();
> $ssh("ssh://aUser:aPass@somehost").cb(ow.format.streamSHPrefix("somehost")).sh("ls").exec()
/*
[somehost] Mail
[somehost] bin
[somehost] tmp
*/

Using arrays with parallel

OpenAF is a mix of Javascript and Java, but "pure" javascript isn't "thread-safe" in the Java world. Nevertheless be...