#
# (c) Jan Gehring <jan.gehring@gmail.com>
#
# vim: set ts=3 sw=3 tw=0:
# vim: set expandtab:
=encoding UTF-8
=head1 NAME
Rex - Remote Execution
=head1 DESCRIPTION
(R)?ex is a small script to ease the execution of remote commands. You can write small tasks in a file named I<Rexfile>.
You can find examples and howtos on L<http://rexify.org/>
=head1 GETTING HELP
=over 4
=item * Web Site: L<http://rexify.org/>
=item * IRC: irc.freenode.net #rex
=item * Bug Tracker: L<https://github.com/krimdomu/Rex/issues>
=item * Twitter: L<http://twitter.com/jfried83>
=back
=head1 SYNOPSIS
user "root";
password "ch4ngem3";
desc "Show Unix version";
task "uname", sub {
say run "uname -a";
};
bash# rex -H "server[01..10]" uname
See L<Rex::Commands> for a list of all commands you can use.
=head1 CLASS METHODS
=over 4
=cut
package Rex;
use strict;
use warnings;
use Net::SSH2;
use Rex::Logger;
use Rex::Interface::Cache;
use Data::Dumper;
use Rex::Interface::Connection;
use Cwd qw(getcwd);
use Rex::Config;
use Rex::Helper::Array;
our (@EXPORT,
$VERSION,
@CONNECTION_STACK,
$GLOBAL_SUDO,
$MODULE_PATHS,
$WITH_EXIT_STATUS);
$VERSION = "0.44.6.1";
my $cur_dir;
BEGIN {
sub _home_dir {
if($^O =~ m/^MSWin/) {
return $ENV{'USERPROFILE'};
}
return $ENV{'HOME'} || "";
}
$cur_dir = getcwd;
unshift(@INC, sub {
my $mod_to_load = $_[1];
return search_module_path($mod_to_load, 1);
});
if(-d "$cur_dir/lib") {
push(@INC, "$cur_dir/lib");
}
my $home_dir = _home_dir();
if(-d "$home_dir/.rex/recipes") {
push(@INC, "$home_dir/.rex/recipes");
}
push(@INC, sub {
my $mod_to_load = $_[1];
return search_module_path($mod_to_load, 0);
});
};
my $home = $ENV{'HOME'};
if($^O =~ m/^MSWin/) {
$home = $ENV{'USERPROFILE'};
}
push(@INC, "$home/.rex/recipes");
sub search_module_path {
my ($mod_to_load, $pre) = @_;
$mod_to_load =~ s/\.pm//g;
my @search_in;
if($pre) {
@search_in = map { ("$_/$mod_to_load.pm") }
grep { -d } @INC;
}
else {
@search_in = map { ("$_/$mod_to_load/__module__.pm", "$_/$mod_to_load/Module.pm") }
grep { -d } @INC;
}
for my $file (@search_in) {
if(-f $file) {
my ($path) = ($file =~ m/^(.*)\/.+?$/);
if($path !~ m/\//) {
$path = $cur_dir . "/$path";
}
# module found, register path
$MODULE_PATHS->{$mod_to_load} = {path => $path};
my $mod_package_name = $mod_to_load;
$mod_package_name =~ s/\//::/g;
$MODULE_PATHS->{$mod_package_name} = {path => $path};
if($pre) {
return;
}
open(my $fh, $file);
return $fh;
}
}
}
sub get_module_path {
my ($module) = @_;
if(exists $MODULE_PATHS->{$module}) {
return $MODULE_PATHS->{$module}->{path};
}
}
sub push_connection {
push @CONNECTION_STACK, $_[0];
return $_[0];
}
sub pop_connection {
pop @CONNECTION_STACK;
Rex::Logger::debug("Connections in queue: " . scalar(@CONNECTION_STACK));
}
sub reconnect_lost_connections {
if(@CONNECTION_STACK > 0) {
Rex::Logger::debug("Need to reinitialize connections.");
for (@CONNECTION_STACK) {
$_->{conn}->reconnect;
}
}
}
# ... no words
my @__modif_caller;
sub unset_modified_caller {
@__modif_caller = ();
}
sub modified_caller {
my (@caller) = @_;
if(@caller) {
@__modif_caller = @caller;
}
else {
return @__modif_caller;
}
}
=item get_current_connection
This function is deprecated since 0.28! See Rex::Commands::connection.
Returns the current connection as a hashRef.
=over 4
=item server
The server name
=item ssh
1 if it is a ssh connection, 0 if not.
=back
=cut
sub get_current_connection {
# if no connection available, use local connect
unless(@CONNECTION_STACK) {
my $conn = Rex::Interface::Connection->create("Local");
Rex::push_connection({
conn => $conn,
ssh => $conn->get_connection_object,
cache => Rex::Interface::Cache->create(),
});
}
$CONNECTION_STACK[-1];
}
=item is_ssh
Returns 1 if the current connection is a ssh connection. 0 if not.
=cut
sub is_ssh {
if($CONNECTION_STACK[-1]) {
my $ref = ref($CONNECTION_STACK[-1]->{"conn"});
if($ref =~ m/SSH/) {
return $CONNECTION_STACK[-1]->{"conn"}->get_connection_object();
}
}
return 0;
}
=item is_local
Returns 1 if the current connection is local. Otherwise 0.
=cut
sub is_local {
if($CONNECTION_STACK[-1]) {
my $ref = ref($CONNECTION_STACK[-1]->{"conn"});
if($ref =~ m/Local/) {
return $CONNECTION_STACK[-1]->{"conn"}->get_connection_object();
}
}
return 0;
}
=item is_sudo
Returns 1 if the current operation is executed within sudo.
=cut
sub is_sudo {
if($GLOBAL_SUDO) { return 1; }
if($CONNECTION_STACK[-1]) {
return $CONNECTION_STACK[-1]->{"use_sudo"};
}
return 0;
}
sub global_sudo {
my ($on) = @_;
$GLOBAL_SUDO = $on;
# turn cache on
Rex::Config->set_use_cache(1);
}
=item get_sftp
Returns the sftp object for the current ssh connection.
=cut
sub get_sftp {
if($CONNECTION_STACK[-1]) {
return $CONNECTION_STACK[-1]->{"conn"}->get_fs_connection_object();
}
return 0;
}
sub get_cache {
if($CONNECTION_STACK[-1]) {
return $CONNECTION_STACK[-1]->{"cache"};
}
return Rex::Interface::Cache->create();
}
=item connect
Use this function to create a connection if you use Rex as a library.
use Rex;
use Rex::Commands::Run;
use Rex::Commands::Fs;
Rex::connect(
server => "remotehost",
user => "root",
password => "f00b4r",
private_key => "/path/to/private/key/file",
public_key => "/path/to/public/key/file",
);
if(is_file("/foo/bar")) {
print "Do something...\n";
}
my $output = run("uptime");
=cut
sub connect {
my ($param) = { @_ };
my $server = $param->{server};
my $port = $param->{port} || 22;
my $timeout = $param->{timeout} || 5;
my $user = $param->{"user"};
my $pass = $param->{"password"};
my $cached_conn = $param->{"cached_connection"};
if(! $cached_conn) {
my $conn = Rex::Interface::Connection->create("SSH");
$conn->connect(
user => $user,
password => $pass,
server => $server,
port => $port,
timeout => $timeout,
%{ $param },
);
unless($conn->is_connected) {
die("Connetion error or refused.");
}
# push a remote connection
my $rex_conn = Rex::push_connection({
conn => $conn,
ssh => $conn->get_connection_object,
server => $server,
cache => Rex::Interface::Cache->create(),
});
# auth unsuccessfull
unless($conn->is_authenticated) {
Rex::Logger::info("Wrong username or password. Or wrong key.", "warn");
# after jobs
die("Wrong username or password. Or wrong key.");
}
return $rex_conn;
}
else {
Rex::push_connection($cached_conn);
return $cached_conn;
}
}
sub deprecated {
my ($func, $version, @msg) = @_;
if($func) {
Rex::Logger::info("The call to $func is deprecated.");
}
if(@msg) {
for (@msg) {
Rex::Logger::info($_);
}
}
Rex::Logger::info("");
Rex::Logger::info("Please rewrite your code. This function will disappear in (R)?ex version $version.");
Rex::Logger::info("If you need assistance please join #rex on irc.freenode.net or our google group.");
}
sub import {
my ($class, $what, $addition1) = @_;
$what ||= "";
my ($register_to, $file, $line) = caller;
if($what eq "-base" || $what eq "base" || $what eq "-feature") {
require Rex::Commands;
Rex::Commands->import(register_in => $register_to);
require Rex::Commands::Run;
Rex::Commands::Run->import(register_in => $register_to);
require Rex::Commands::Fs;
Rex::Commands::Fs->import(register_in => $register_to);
require Rex::Commands::File;
Rex::Commands::File->import(register_in => $register_to);
require Rex::Commands::Download;
Rex::Commands::Download->import(register_in => $register_to);
require Rex::Commands::Upload;
Rex::Commands::Upload->import(register_in => $register_to);
require Rex::Commands::Gather;
Rex::Commands::Gather->import(register_in => $register_to);
require Rex::Commands::Kernel;
Rex::Commands::Kernel->import(register_in => $register_to);
require Rex::Commands::Pkg;
Rex::Commands::Pkg->import(register_in => $register_to);
require Rex::Commands::Service;
Rex::Commands::Service->import(register_in => $register_to);
require Rex::Commands::Sysctl;
Rex::Commands::Sysctl->import(register_in => $register_to);
require Rex::Commands::Tail;
Rex::Commands::Tail->import(register_in => $register_to);
require Rex::Commands::Process;
Rex::Commands::Process->import(register_in => $register_to);
require Rex::Commands::Sync;
Rex::Commands::Sync->import(register_in => $register_to);
}
if($what eq "-feature" || $what eq "feature") {
if(! ref($addition1)) {
$addition1 = [$addition1];
}
for my $add (@{ $addition1 }) {
my $found_feature = 0;
if($add =~ m/^(\d+\.\d+)$/) {
my $vers = $1;
my ($major, $minor, $patch) = split(/\./, $VERSION);
my ($c_major, $c_minor) = split(/\./, $vers);
if( ($c_major > $major)
||
($c_major >= $major && $c_minor > $minor)
) {
Rex::Logger::info("This Rexfile tries to enable features that are not supported with your version. Please update.", "warn");
exit 1;
}
}
# remove default task auth
if($add =~ m/^\d+\.\d+$/ && $add >= 0.31) {
Rex::Logger::debug("activating featureset >= 0.31");
Rex::TaskList->create()->set_default_auth(0);
$found_feature = 1;
}
if($add =~ m/^\d+\.\d+$/ && $add >= 0.35) {
Rex::Logger::debug("activating featureset >= 0.35");
$Rex::Commands::REGISTER_SUB_HASH_PARAMTER = 1;
$found_feature = 1;
}
if($add =~ m/^\d+\.\d+$/ && $add >= 0.40) {
Rex::Logger::debug("activating featureset >= 0.40");
$Rex::Template::BE_LOCAL = 1;
$Rex::WITH_EXIT_STATUS = 1;
$found_feature = 1;
}
if($add eq "no_local_template_vars") {
Rex::Logger::debug("activating featureset no_local_template_vars");
$Rex::Template::BE_LOCAL = 0;
$found_feature = 1;
}
if($add eq "exit_status") {
Rex::Logger::debug("activating featureset exit_status");
$Rex::WITH_EXIT_STATUS = 1;
$found_feature = 1;
}
if($add eq "sudo_without_sh") {
Rex::Logger::debug("using sudo without sh. this might break some things.");
Rex::Config->set_sudo_without_sh(1);
$found_feature = 1;
}
if($add eq "sudo_without_locales") {
Rex::Logger::debug("Using sudo without locales. this _will_ break things!");
Rex::Config->set_sudo_without_locales(1);
$found_feature = 1;
}
if($add eq "no_tty") {
Rex::Logger::debug("Disabling pty usage for ssh");
Rex::Config->set_no_tty(1);
$found_feature = 1;
}
if($add eq "empty_groups") {
Rex::Logger::debug("Enabling usage of empty groups");
Rex::Config->set_allow_empty_groups(1);
$found_feature = 1;
}
if($add eq "use_server_auth") {
Rex::Logger::debug("Enabling use_server_auth");
Rex::Config->set_use_server_auth(1);
$found_feature = 1;
}
if($add eq "exec_and_sleep") {
Rex::Logger::debug("Enabling exec_and_sleep");
Rex::Config->set_sleep_hack(1);
$found_feature = 1;
}
if($add eq "disable_strict_host_key_checking") {
Rex::Logger::debug("Disabling strict host key checking for openssh");
Rex::Config->set_openssh_opt(StrictHostKeyChecking => "no");
$found_feature = 1;
}
if($add eq "reporting" || $add eq "report" || exists $ENV{REX_REPORT_TYPE}) {
Rex::Logger::debug("Enabling reporting");
Rex::Config->set_do_reporting(1);
$found_feature = 1;
}
if($add eq "source_profile") {
Rex::Logger::debug("Enabling source_profile");
Rex::Config->set_source_profile(1);
$found_feature = 1;
}
if($add eq "source_global_profile") {
Rex::Logger::debug("Enabling source_global_profile");
Rex::Config->set_source_global_profile(1);
$found_feature = 1;
}
if($add eq "no_path_cleanup") {
Rex::Logger::debug("Enabling no_path_cleanup");
Rex::Config->set_no_path_cleanup(1);
$found_feature = 1;
}
if($add eq "exec_autodie") {
Rex::Logger::debug("Enabling exec_autodie");
Rex::Config->set_exec_autodie(1);
$found_feature = 1;
}
if($found_feature == 0) {
Rex::Logger::info("You tried to load a feature ($add) that doesn't exists in your Rex version. Please update.", "warn");
exit 1;
}
}
}
if(exists $ENV{REX_REPORT_TYPE}) {
Rex::Logger::debug("Enabling reporting");
Rex::Config->set_do_reporting(1);
}
# we are always strict
strict->import;
}
=back
=head1 CONTRIBUTORS
Many thanks to the contributors for their work (alphabetical order).
=over 4
=item Alexandr Ciornii
=item Anders Ossowicki
=item Andrej Zverev
=item Boris Däppen
=item Chris Steigmeier
=item complefor
=item Cuong Manh Le
=item Daniel Baeurer
=item Dominik Danter
=item Dominik Schulz
=item fanyeren
=item Ferenc Erki
=item Fran Rodriguez
=item Franky Van Liedekerke
=item Gilles Gaudin, for writing a french howto
=item Hiroaki Nakamura
=item Jean Charles Passard
=item Jeen Lee
=item Jonathan Delgado
=item Jon Gentle
=item Joris
=item Jose Luis Martinez
=item Kasim Tuman
=item Keedi Kim
=item Laird Liu
=item Mario Domgoergen
=item Naveed Massjouni
=item Nikolay Fetisov
=item Nils Domrose
=item Peter H. Ezetta
=item Piotr Karbowski
=item Rao Chenlin (Chenryn)
=item RenatoCRON
=item Samuele Tognini
=item Sascha Guenther
=item Simon Bertrang
=item Stephane Benoit
=item Sven Dowideit
=item Tianon Gravi
=item Tokuhiro Matsuno
=item Tomohiro Hosaka
=back
=cut
1;