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...
1 comment:
Very nice!
Would it be possible/easy to implement an Erlang RPC service using the SSH stuff?
Cheers, Tobbe
Post a Comment