суббота, 16 апреля 2011 г.

Удаленное выполнение команд через SSH в Ерланге

While designing my monitoring tool, and working and treregex, I found the ssh documentation and realize that it can be very useful for my tool.

A simple question needed to be answered, is the ssh module able to easily spawn a remote process for me ?
To verify, I tried to build a remote tail module called ssh_tail :)

-module(ssh_tail).
        
-export([tail/3]).
-define(TIMEOUT, 5000). 

tail(Host, User, Pass) ->
 case ssh_cm:connect(Host, 22, [{user_dir, "/var/tmp/ssh"}, {user, User}, {password, Pass}]) of
  {ok, CM} ->
   session(CM, fun(X) -> io:format("-ssh: ~p~n", [X]) end);

  Error ->
   Error
 end.

From the ssh documentation user_dir let you decide where you want to store keys, from my experience it's better to use a separate directory from the ~/.ssh.
It happens that latest version of ssh add meta information to their files that the ssh module can't handle. (more on this in another post).

For the test I wanted to do a "tail -f" on a specific file ie "/var/log/syslog".
session(CM, Callback) ->
 case ssh_cm:session_open(CM, ?TIMEOUT) of
  {ok, Channel} ->
   case ssh_cm:shell(CM, Channel) of 
    ok ->
     ssh_cm:send(CM, Channel, "tail --follow=name /var/log/syslog\n"),
     ssh_loop(CM, Channel, Callback);

    Error ->
     error_logger:error_msg("Error: ~p~n", [Error])
   end;
  Error ->
   error_logger:error_msg("Session Error: ~p~n", [Error])
 end.

ssh_cm is responsible for starting a shell, and sending commands to the remote shell process. I send
tail --follow=name /var/log/syslog\n

Don't forget the final '\n' character, since you won't get any results if you don't send it :p
(I didn't think of that while testing for the first time and think that the code didn't work at all...)
ssh_loop(CM, Channel, Callback) ->
 receive
  stop ->
   % Closing channel
   % ssh_cm:detach(CM, ?TIMEOUT),
   ssh_cm:close(CM, Channel);

  {ssh_cm, CM, {data, _Channel, 0, Data}} ->
   Callback(Data), 
   ssh_loop(CM, Channel, Callback);

  {ssh_cm, CM, {data, Channel, Type, Data}} ->
   io:format("extended (~p): ~p~n", [Type, Data]),
   ssh_loop(CM, Channel, Callback);

  {ssh_cm, CM, {closed, _Channel}} ->
   ssh_cm:detach(CM, ?TIMEOUT);
   
  E ->
   error_logger:info_msg("[~p] Received: ~p~n", [?MODULE, E]),
   ssh_loop(CM, Channel, Callback) 
 end.

ssh_cm sends various message to the calling process, more important tuples are
{ssh_cm, CM, {data, _Channel, 0, Data}}

Data holds what you want, and in our case a line sent by the tail process...
The callback defined at the beginning is then executed:
tail(Host, User, Pass) ->
 case ssh_cm:connect(Host, 22, [{user_dir, "/var/tmp/ssh"}, {user, User}, {password, Pass}]) of
  {ok, CM} ->
   session(CM, 
    fun(X) ->  % Our Callback 
     io:format("-ssh: ~p~n", [X]) % simple display...
    end);

  Error ->
   Error
 end.


To conclude this simple module is able to spawn a remote "tail -f" using a ssh connection and using a callback function on every data received.

The code was designed from the ssh_ssh module that you can find in the ssh module source code, because the ssh documentation is really sparse for now...
Оригинал

Комментариев нет:

Отправить комментарий