From 1c16198535dd620a36d7d3b77d7c61aa2d9f8814 Mon Sep 17 00:00:00 2001 From: krimdomu Date: Sat, 30 Jul 2016 14:29:12 +0200 Subject: [PATCH 1/7] * Added Controller class * Refactored resources so that resources and functions work similar * resources now also can have multiple code and type checking --- lib/Rex.pm | 3 + lib/Rex/Controller.pm | 35 ++++++++ lib/Rex/Controller/Function.pm | 18 ++++ lib/Rex/Controller/Resource.pm | 28 +++++++ lib/Rex/Function/Common.pm | 3 +- lib/Rex/Function/Fs/is_file.pm | 4 +- lib/Rex/Function/run.pm | 9 +- lib/Rex/Resource.pm | 85 ++++++------------- lib/Rex/Resource/Common.pm | 149 ++++++++++++++++++++++++++++++--- lib/Rex/Resource/kernel.pm | 49 ++++++++--- 10 files changed, 296 insertions(+), 87 deletions(-) create mode 100644 lib/Rex/Controller.pm create mode 100644 lib/Rex/Controller/Function.pm create mode 100644 lib/Rex/Controller/Resource.pm diff --git a/lib/Rex.pm b/lib/Rex.pm index 830175812..f72b40f4c 100644 --- a/lib/Rex.pm +++ b/lib/Rex.pm @@ -94,6 +94,9 @@ BEGIN { use Rex::Report; use Rex::Notify; use Rex::Require; + use Rex::Controller; + use Rex::Controller::Function; + use Rex::Controller::Resource; use File::Basename; eval { Net::SSH2->require; }; } diff --git a/lib/Rex/Controller.pm b/lib/Rex/Controller.pm new file mode 100644 index 000000000..e70358596 --- /dev/null +++ b/lib/Rex/Controller.pm @@ -0,0 +1,35 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::Controller; + +use strict; +use warnings; + +# VERSION + +use Moose; + +has app => ( + is => 'ro', + isa => 'Rex', + lazy => 1, + default => sub { + Rex->instance; + }, +); + +has params => ( + is => 'ro', + isa => 'HashRef' +); + +sub param { + my ( $self, $what ) = @_; + return $self->params->{$what} if ($what); +} + +1; diff --git a/lib/Rex/Controller/Function.pm b/lib/Rex/Controller/Function.pm new file mode 100644 index 000000000..75ed4a817 --- /dev/null +++ b/lib/Rex/Controller/Function.pm @@ -0,0 +1,18 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::Controller::Function; + +use strict; +use warnings; + +# VERSION + +use Moose; + +extends qw(Rex::Controller::Resource); + +1; diff --git a/lib/Rex/Controller/Resource.pm b/lib/Rex/Controller/Resource.pm new file mode 100644 index 000000000..ccb9ca66e --- /dev/null +++ b/lib/Rex/Controller/Resource.pm @@ -0,0 +1,28 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::Controller::Resource; + +use strict; +use warnings; + +# VERSION + +use Moose; + +extends qw(Rex::Controller); + +has params => ( + is => 'ro', + isa => 'HashRef' +); + +sub param { + my ( $self, $what ) = @_; + return $self->params->{$what} if ($what); +} + +1; diff --git a/lib/Rex/Function/Common.pm b/lib/Rex/Function/Common.pm index 26f1ce7a5..8b4bbd516 100644 --- a/lib/Rex/Function/Common.pm +++ b/lib/Rex/Function/Common.pm @@ -37,6 +37,7 @@ sub function { $options->{params_list} //= [ name => { isa => 'Str' }, ]; my $app = Rex->instance; + my $c = Rex::Controller::Function->new( app => $app ); push @{ $__lookup_table->{$name} }, { @@ -94,7 +95,7 @@ sub function { # * unless # * creates - $ret = $f->{code}->( $app, @args, %arg_options ); + $ret = $f->{code}->( $c, @args, %arg_options ); last; } if ( !$found ) { diff --git a/lib/Rex/Function/Fs/is_file.pm b/lib/Rex/Function/Fs/is_file.pm index c3ce103ef..99a3b067e 100644 --- a/lib/Rex/Function/Fs/is_file.pm +++ b/lib/Rex/Function/Fs/is_file.pm @@ -32,11 +32,11 @@ function "is_file", { }, # this is the code that will be executed - # first parameter of the code reference is always the Rex application object. + # first parameter of the code reference is always a Rex controller object. # second, third, ... parameter are the real parameter the user passed to the # function call. sub { - my ( $app, $file ) = @_; + my ( $c, $file ) = @_; $file = resolv_path($file); my $fs = Rex::Interface::Fs->create; diff --git a/lib/Rex/Function/run.pm b/lib/Rex/Function/run.pm index 90a8c1df6..625f187f8 100644 --- a/lib/Rex/Function/run.pm +++ b/lib/Rex/Function/run.pm @@ -65,7 +65,7 @@ function "run", { }; sub _run_command { - my $app = shift; + my $c = shift; my $cmd = shift; my ( $args, $options, $path ); @@ -103,10 +103,13 @@ sub _run_command { } if ( $? == 127 ) { - $app->output->stash( error_info => "Command not found.", error_code => $? ); + $c->app->output->stash( + error_info => "Command not found.", + error_code => $? + ); } elsif ( $? != 0 ) { - $app->output->stash( error_info => "Return code: $?", error_code => $? ); + $c->app->output->stash( error_info => "Return code: $?", error_code => $? ); } my $ret = {}; diff --git a/lib/Rex/Resource.pm b/lib/Rex/Resource.pm index b15eb5cb5..b686d1103 100644 --- a/lib/Rex/Resource.pm +++ b/lib/Rex/Resource.pm @@ -11,63 +11,32 @@ use warnings; # VERSION +use Moose; + use Rex::Constants; -use Hash::Merge qw/merge/; require Rex::Resource::Common; our @CURRENT_RES; +has name => ( is => 'ro', isa => 'Str', ); +has display_name => ( is => 'ro', isa => 'Str', ); +has type => ( is => 'ro', isa => 'Str', ); +has cb => ( is => 'ro', isa => 'CodeRef' ); +has __status__ => + ( is => 'ro', isa => 'Str', writer => '_set_status', default => "unchanged" ); +has status_message => ( + is => 'ro', + isa => 'Str', + default => sub { "" }, + writer => "_set_status_message" +); + sub is_inside_resource { ref $CURRENT_RES[-1] ? 1 : 0 } sub get_current_resource { $CURRENT_RES[-1] } -sub new { - my $that = shift; - my $proto = ref($that) || $that; - my $self = {@_}; - - bless( $self, $proto ); - - $self->{__status__} = "unchanged"; - - return $self; -} - -sub name { (shift)->{name}; } -sub display_name { (shift)->{display_name}; } -sub type { (shift)->{type}; } - sub call { - - if ( ref $_[1] eq "HASH" ) { - my ( $self, $name ) = @_; - - # multiple resource call - for my $n ( keys %{$name} ) { - my $this_p = $name->{$n}; - if ( $_[2] && ref $_[2] eq "HASH" ) { - - # some defaults to merge - $this_p = merge( $this_p, $_[2] ); - } - - $self->call( $n, %{$this_p} ); - } - - return; - } - - my ( $self, $name, %params ) = @_; - - if ( ref $name eq "ARRAY" ) { - - # multiple resource call - for my $n ( @{$name} ) { - $self->call( $n, %params ); - } - - return; - } + my ( $self, $c, $name, %params ) = @_; push @CURRENT_RES, $self; @@ -96,7 +65,7 @@ sub call { my $failed_msg = ""; eval { - my ( $provider, $mod_config ) = $self->{cb}->( \%params ); + my ( $provider, $mod_config ) = $self->cb->( $c, \%params ); if ( $provider =~ m/^[a-zA-Z0-9_:]+$/ && ref $mod_config eq "HASH" ) { @@ -141,7 +110,7 @@ sub call { Rex::get_current_connection()->{reporter}->report( changed => 1, failed => $failed, - message => $self->message, + message => $self->status_message, ); } else { @@ -153,7 +122,7 @@ sub call { } if ( exists $params{on_change} && $self->was_updated ) { - $params{on_change}->( $self->{__status__} ); + $params{on_change}->( $self->__status__ ); } Rex::get_current_connection()->{reporter} @@ -179,10 +148,10 @@ sub changed { my ( $self, $changed ) = @_; if ( defined $changed ) { - $self->{__status__} = "changed"; + $self->_set_status("changed"); } else { - return ( $self->{__status__} eq "changed" ? 1 : 0 ); + return ( $self->__status__ eq "changed" ? 1 : 0 ); } } @@ -190,10 +159,10 @@ sub created { my ( $self, $created ) = @_; if ( defined $created ) { - $self->{__status__} = "created"; + $self->_set_status("created"); } else { - return ( $self->{__status__} eq "created" ? 1 : 0 ); + return ( $self->__status__ eq "created" ? 1 : 0 ); } } @@ -201,10 +170,10 @@ sub removed { my ( $self, $removed ) = @_; if ( defined $removed ) { - $self->{__status__} = "removed"; + $self->_set_status("removed"); } else { - return ( $self->{__status__} eq "removed" ? 1 : 0 ); + return ( $self->__status__ eq "removed" ? 1 : 0 ); } } @@ -212,10 +181,10 @@ sub message { my ( $self, $message ) = @_; if ( defined $message ) { - $self->{message} = $message; + $self->_set_status_message($message); } else { - return ( $self->{message} || ( $self->display_name . " changed." ) ); + return ( $self->status_message || ( $self->display_name . " changed." ) ); } } diff --git a/lib/Rex/Resource/Common.pm b/lib/Rex/Resource/Common.pm index 888d2df7b..6e2d680fb 100644 --- a/lib/Rex/Resource/Common.pm +++ b/lib/Rex/Resource/Common.pm @@ -16,6 +16,9 @@ require Rex::Config; use Rex::Commands::Gather; use Rex::Resource; use Data::Dumper; +use MooseX::Params::Validate; +use Hash::Merge qw/merge/; + use base qw(Exporter); use vars qw(@EXPORT); @@ -59,6 +62,8 @@ sub emit { =cut +my $__lookup_table; + sub resource { my ( $name, $options, $function ) = @_; my $name_save = $name; @@ -79,19 +84,143 @@ sub resource { die "Wrong resource name syntax."; } + push @{ $__lookup_table->{$name} }, + { + options => $options, + code => $function, + }; + my ( $class, $file, @tmp ) = caller; - my $res = Rex::Resource->new( - type => "${class}::$name", - name => $name, - display_name => ( - $options->{name} - || ( $options->{export} ? $name : "${caller_pkg}::${name}" ) - ), - cb => $function - ); + # this function is responsible to lookup the right resource code + # every resource can have multiple resource code depending on its parameters. + my $call_func = sub { + my $app = Rex->instance; + + $app->output->print_s( { title => $name, msg => $_[0] } ); + + eval { + my $found = 0; + for my $f ( + sort { + scalar( @{ $b->{options}->{params_list} } ) <=> + scalar( @{ $a->{options}->{params_list} } ) + } @{ $__lookup_table->{$name} } + ) + { + my %args; + eval { + my @modified_args = @_; + my $name = shift @modified_args; + + # some defaults maybe a coderef, so we need to execute this now + my @_x = @{ $f->{options}->{params_list} }; + my %_x = @_x; + for my $k ( keys %_x ) { + if ( ref $_x{$k}->{default} eq "CODE" ) { + $_x{$k}->{default} = $_x{$k}->{default}->(@_); + } + } + %args = validated_hash( + \@modified_args, %_x, + MX_PARAMS_VALIDATE_NO_CACHE => 1, + MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1 + ); + + $found = 1; + 1; + } or do { + + # print "Err: $@\n"; + # TODO catch no "X parameter was given" errors + next; + }; + + # TODO check for common parameters like + # * timeout + # * only_notified + # * only_if + # * unless + # * creates + # * on_change + # * ensure + + my $res = Rex::Resource->new( + type => "${class}::$name", + name => $name, + display_name => ( + $options->{name} + || ( $options->{export} ? $name : "${caller_pkg}::${name}" ) + ), + cb => $f->{code}, + ); + + my $c = Rex::Controller::Resource->new( app => $app, params => \%args ); + $res->call( $c, @_ ); + last; + } + if ( !$found ) { + die "Resource $name for provided parameter not found."; + } + 1; + } or do { + $app->output->endln_failed(); + die "Error executing resource: $name.\nError: $@\n"; + }; + + $app->output->endln_ok(); + }; + + # this is the code that gets registered into the namespace my $func = sub { - $res->call(@_); + + # test if first parameter to resource is a hash + # if so, we have multiple instances of this resource and we need to call + # every of them. + # + # Example: + # kmod { + # foo => { ensure => "present", }, + # bar => { ensure => "present", }, + # }; + if ( ref $_[0] eq "HASH" ) { + my ($res_hash) = @_; + + for my $n ( keys %{$res_hash} ) { + my $this_p = $res_hash->{$n}; + + # test if the second parameter is also a hash, this means we need to + # merge this hash as default values into the parameters of the + # first hash. + if ( $_[1] && ref $_[1] eq "HASH" ) { + $this_p = merge( $this_p, $_[1] ); + } + + # now we call the resource code for each one of the first hash. + $call_func->( $n, %{$this_p} ); + } + + # nothing to do anymore, so we can just return. + # a resource doesn't have a specified return value. + return undef; + } + + # test if the first parameter to resource is an array + # if so, we have to iterate over the array and call the resource code for + # each of them. + if ( ref $_[0] eq "ARRAY" ) { + my $all_res = shift; + for my $n ( @{$all_res} ) { + $call_func->( $n, @_ ); + } + + # nothing to do anymore, so we can just return. + # a resource doesn't have a specified return value. + return undef; + } + + # default resource call + $call_func->(@_); }; if (!$class->can($name) diff --git a/lib/Rex/Resource/kernel.pm b/lib/Rex/Resource/kernel.pm index 36df5c6a0..3b8764894 100644 --- a/lib/Rex/Resource/kernel.pm +++ b/lib/Rex/Resource/kernel.pm @@ -38,24 +38,47 @@ use Rex -minimal; use Rex::Commands::Gather; use Rex::Resource::Common; +use Data::Dumper; use Carp; -resource "kmod", { export => 1 }, sub { - my $mod_name = resource_name; - - my $mod_config = { - ensure => param_lookup( "ensure", "present" ), - name => $mod_name, - }; - - my $provider = - param_lookup( "provider", - get_resource_provider( kernelname(), operating_system() ) ); +# create the resource "kmod" +resource "kmod", { + + # export this resource to the main namespace. so that it can be used + # directly in Rexfile without the need to prepend the namespace of the module. + export => 1, + + # define the parameter this resource will have + # rex is doing a type check here. + params_list => [ + mod => { + isa => 'Str', + + # default is the name parameter (kmod $name, ensure => "present";) + default => sub { shift } + }, + ensure => { + isa => 'Str', + default => sub { "present" } + }, + ], + }, + sub { + my ($c) = @_; + + # here we define the provider the resource should use. If someone want to use + # a custom provider we will use this. Otherwise we try to detect the provider + # automatically. + my $provider = $c->param("provider") + || get_resource_provider( kernelname(), operating_system() ); Rex::Logger::debug("Get kernel provider: $provider"); - return ( $provider, $mod_config ); -}; + # at the end we return the wanted provider and an hash reference containing + # all the parameters for this resource. + # here we just pass the parameters back without modifying them. + return ( $provider, $c->params ); + }; 1; From c761690e9a43526047a3789a9036021cc293d4f8 Mon Sep 17 00:00:00 2001 From: krimdomu Date: Sat, 30 Jul 2016 18:31:29 +0200 Subject: [PATCH 2/7] refactored file resource for new syntax --- lib/Rex/Controller/Resource.pm | 12 +++++-- lib/Rex/Resource/Common.pm | 11 +++++- lib/Rex/Resource/file.pm | 65 ++++++++++++++++++++-------------- 3 files changed, 59 insertions(+), 29 deletions(-) diff --git a/lib/Rex/Controller/Resource.pm b/lib/Rex/Controller/Resource.pm index ccb9ca66e..866a8539b 100644 --- a/lib/Rex/Controller/Resource.pm +++ b/lib/Rex/Controller/Resource.pm @@ -16,8 +16,9 @@ use Moose; extends qw(Rex::Controller); has params => ( - is => 'ro', - isa => 'HashRef' + is => 'ro', + isa => 'HashRef', + writer => '_set_params', ); sub param { @@ -25,4 +26,11 @@ sub param { return $self->params->{$what} if ($what); } +sub set_param { + my ( $self, $what, $value ) = @_; + my $p = $self->params; + $p->{$what} = $value; + $self->_set_params($p); +} + 1; diff --git a/lib/Rex/Resource/Common.pm b/lib/Rex/Resource/Common.pm index 6e2d680fb..9a016654c 100644 --- a/lib/Rex/Resource/Common.pm +++ b/lib/Rex/Resource/Common.pm @@ -21,6 +21,7 @@ use Hash::Merge qw/merge/; use base qw(Exporter); use vars qw(@EXPORT); +use Carp; @EXPORT = qw(emit resource resource_name changed created removed get_resource_provider); @@ -99,6 +100,7 @@ sub resource { $app->output->print_s( { title => $name, msg => $_[0] } ); + my @errors; eval { my $found = 0; for my $f ( @@ -130,6 +132,7 @@ sub resource { $found = 1; 1; } or do { + push @errors, $@; # print "Err: $@\n"; # TODO catch no "X parameter was given" errors @@ -160,7 +163,13 @@ sub resource { last; } if ( !$found ) { - die "Resource $name for provided parameter not found."; + my @err_msg; + for my $err (@errors) { + my ($fline) = split( /\n/, $err ); + push @err_msg, $fline; + } + croak "Resource $name for provided parameter not found.\nErrors:\n" + . join( "\n", @err_msg ); } 1; } or do { diff --git a/lib/Rex/Resource/file.pm b/lib/Rex/Resource/file.pm index 659bbb2f7..cd943389a 100644 --- a/lib/Rex/Resource/file.pm +++ b/lib/Rex/Resource/file.pm @@ -127,43 +127,56 @@ use Rex -minimal; use Rex::Commands::Gather; use Rex::Resource::Common; use Rex::Helper::Path; +use Moose::Util::TypeConstraints; use Carp; -resource "file", { export => 1 }, sub { - my $file_name = resolv_path( resource_name() ); - - my $file_config = { - ensure => param_lookup( "ensure", "present" ), - name => $file_name, - mode => param_lookup( "mode", undef ), - owner => param_lookup( "owner", undef ), - group => param_lookup( "group", undef ), - source => param_lookup( "source", undef ), - content => param_lookup( "content", undef ), - no_overwrite => param_lookup( "no_overwrite", undef ), - not_recursive => param_lookup( "not_recursive", 0 ), - }; - - my $provider = - param_lookup( "provider", - get_resource_provider( kernelname(), operating_system() ) ); +subtype 'OctedNum', as 'Str', + where { $_ =~ m/^[0-8]{4}$/ }, + message { "The given string ($_) is not octal." }; + +resource "file", { + export => 1, + params_list => [ + name => { + isa => 'Str', + default => sub { shift } + }, + ensure => { + isa => 'Str', + default => sub { "present" } + }, + mode => { isa => 'OctedNum', default => undef }, + owner => { isa => 'Str', default => undef }, + group => { isa => 'Str', default => undef }, + source => { isa => 'Str', default => undef }, + content => { isa => 'Str', default => undef }, + no_overwrite => { isa => 'Bool | Undef', default => undef }, + not_recursive => { isa => 'Bool', default => 0 }, + ], + }, + sub { + my ($c) = @_; + my $file_name = resolv_path( $c->param("name") ); + + my $provider = $c->param("provider") + || get_resource_provider( kernelname(), operating_system() ); Rex::Logger::debug("Get file provider: $provider"); - if ( defined $file_config->{source} ) { - $file_config->{source} = - get_file_path( resolv_path( $file_config->{source} ), caller() ); + if ( defined $c->param("source") ) { + $c->set_param( "source", + get_file_path( resolv_path( $c->param("source") ), caller() ) ); if ( Rex::Config->get_environment - && -f $file_config->{source} . "." . Rex::Config->get_environment ) + && -f $c->param("source") . "." . Rex::Config->get_environment ) { - $file_config->{source} = - $file_config->{source} . "." . Rex::Config->get_environment; + $c->set_param( "source", + $c->param("source") . "." . Rex::Config->get_environment ); } } - return ( $provider, $file_config ); -}; + return ( $provider, $c->params ); + }; 1; From 8ea4f3c36b5d1d075d22dfda0a68c499539e0d44 Mon Sep 17 00:00:00 2001 From: krimdomu Date: Sat, 30 Jul 2016 21:57:50 +0200 Subject: [PATCH 3/7] firewall provider --- lib/Rex/Resource.pm | 1 + lib/Rex/Resource/Role/Ensureable.pm | 8 +- lib/Rex/Resource/firewall.pm | 142 ++++++++--------- lib/Rex/Resource/firewall/Provider/base.pm | 35 +---- .../Resource/firewall/Provider/iptables.pm | 143 +++++------------- lib/Rex/Resource/firewall/Provider/ufw.pm | 14 +- 6 files changed, 128 insertions(+), 215 deletions(-) diff --git a/lib/Rex/Resource.pm b/lib/Rex/Resource.pm index b686d1103..d22a31316 100644 --- a/lib/Rex/Resource.pm +++ b/lib/Rex/Resource.pm @@ -71,6 +71,7 @@ sub call { # new resource interface # old one is already executed via $self->{cb}->(\%params) + $provider->require; my $provider_o = $provider->new( type => $self->type, diff --git a/lib/Rex/Resource/Role/Ensureable.pm b/lib/Rex/Resource/Role/Ensureable.pm index d270aa53d..ab1eb687a 100644 --- a/lib/Rex/Resource/Role/Ensureable.pm +++ b/lib/Rex/Resource/Role/Ensureable.pm @@ -37,14 +37,18 @@ sub process { . " not a valid option for 'ensure'."; } - if ( !$self->test ) { + my $res_available = $self->test; + + if ( ( $ensure_func eq "present" && !$res_available ) + || ( $ensure_func eq "absent" && $res_available ) + || ( $ensure_func ne "present" && $ensure_func ne "absent" ) ) + { #### check and run before_change hook Rex::Hook::run_hook( $self->type => "before_change", $self->name, %{ $self->config } ); ############################## - $self->$ensure_func; #### check and run after_change hook diff --git a/lib/Rex/Resource/firewall.pm b/lib/Rex/Resource/firewall.pm index 29f4cdb57..58bc2cd38 100644 --- a/lib/Rex/Resource/firewall.pm +++ b/lib/Rex/Resource/firewall.pm @@ -75,76 +75,82 @@ my $__provider = { default => "Rex::Resource::firewall::Provider::iptables", }; =cut -resource "firewall", { export => 1 }, sub { - my $rule_name = resource_name; - - my $rule_config = { - action => param_lookup("action"), - ensure => param_lookup( "ensure", "present" ), - proto => param_lookup( "proto", undef ), - source => param_lookup( "source", undef ), - destination => param_lookup( "destination", undef ), - port => param_lookup( "port", undef ), - app => param_lookup( "app", undef ), - sport => param_lookup( "sport", undef ), - sapp => param_lookup( "sapp", undef ), - dport => param_lookup( "dport", undef ), - dapp => param_lookup( "dapp", undef ), - tcp_flags => param_lookup( "tcp_falgs", undef ), - chain => param_lookup( "chain", "input" ), - table => param_lookup( "table", "filter" ), - iniface => param_lookup( "iniface", undef ), - outiface => param_lookup( "outiface", undef ), - reject_with => param_lookup( "reject_with", undef ), - logging => param_lookup( "logging", undef ), # overall logging - log => param_lookup( "log", undef ), # logging for rule - log_level => param_lookup( "log_level", undef ), # logging for rule - log_prefix => param_lookup( "log_prefix", undef ), - state => param_lookup( "state", undef ), - ip_version => param_lookup( "ip_version", -4 ), - }; +resource "firewall", { + export => 1, + params_list => [ + name => { + isa => 'Str', + default => sub { shift } + }, + ensure => { + isa => 'Str', + default => sub { "present" } + }, + action => { isa => 'Str', }, + proto => { isa => 'Str | Undef', default => "tcp" }, + source => { isa => 'Str | Undef', default => undef }, + destination => { isa => 'Str | Undef', default => undef }, + port => { isa => 'Int | Undef', default => undef }, + source => { isa => 'Str | Undef', default => undef }, + app => { isa => 'Str | Undef', default => undef }, + sport => { isa => 'Int | Undef', default => undef }, + dport => { + isa => 'Int | Undef', + default => sub { my ( $name, %p ) = @_; return $p{port}; }, + }, + dapp => { isa => 'Str | Undef', default => undef }, + tcp_flags => { isa => 'ArrayRef | Undef', default => undef }, + chain => { isa => 'Str | Undef', default => "INPUT" }, + table => { isa => 'Str | Undef', default => undef }, + iniface => { isa => 'Str | Undef', default => undef }, + outiface => { isa => 'Str | Undef', default => undef }, + reject_with => { isa => 'Str | Undef', default => undef }, + logging => { isa => 'Str | Undef', default => undef }, + log => { isa => 'Str | Undef', default => undef }, + log_level => { isa => 'Str | Undef', default => undef }, + log_prefix => { isa => 'Str | Undef', default => undef }, + state => { isa => 'Str | Undef', default => undef }, + ip_version => { isa => 'Str | Undef', default => "-4" }, + ], + }, + sub { + my ($c) = @_; my $provider = - param_lookup( "provider", case ( lc(operating_system), $__provider ) ); - - if ( $provider !~ m/::/ ) { - $provider = "Rex::Resource::firewall::Provider::$provider"; - } - - $provider->require; - my $provider_o = $provider->new(); - - my $changed = 0; - if ( my $logging = $rule_config->{logging} ) { - if ( $provider_o->logging($logging) ) { - emit changed, "Firewall logging updated."; - } - } - elsif ( $rule_config->{ensure} eq "present" ) { - if ( $provider_o->present($rule_config) ) { - emit created, "Firewall rule created."; - } - } - elsif ( $rule_config->{ensure} eq "absent" ) { - if ( $provider_o->absent($rule_config) ) { - emit removed, "Firewall rule removed."; - } - } - elsif ( $rule_config->{ensure} eq "disabled" ) { - if ( $provider_o->disable($rule_config) ) { - emit changed, "Firewall disabled."; - } - } - elsif ( $rule_config->{ensure} eq "enabled" ) { - if ( $provider_o->enable($rule_config) ) { - emit changed, "Firewall enabled."; - } - } - else { - die "Error: $rule_config->{ensure} not a valid option for 'ensure'."; - } - -}; + $c->param("provider") || case ( lc(operating_system), $__provider ); + + return ( $provider, $c->params ); + + # if ( my $logging = $rule_config->{logging} ) { + # if ( $provider_o->logging($logging) ) { + # emit changed, "Firewall logging updated."; + # } + # } + # elsif ( $rule_config->{ensure} eq "present" ) { + # if ( $provider_o->present($rule_config) ) { + # emit created, "Firewall rule created."; + # } + # } + # elsif ( $rule_config->{ensure} eq "absent" ) { + # if ( $provider_o->absent($rule_config) ) { + # emit removed, "Firewall rule removed."; + # } + # } + # elsif ( $rule_config->{ensure} eq "disabled" ) { + # if ( $provider_o->disable($rule_config) ) { + # emit changed, "Firewall disabled."; + # } + # } + # elsif ( $rule_config->{ensure} eq "enabled" ) { + # if ( $provider_o->enable($rule_config) ) { + # emit changed, "Firewall enabled."; + # } + # } + # else { + # die "Error: $rule_config->{ensure} not a valid option for 'ensure'."; + # } + + }; =back diff --git a/lib/Rex/Resource/firewall/Provider/base.pm b/lib/Rex/Resource/firewall/Provider/base.pm index 48565ec49..f0a69e0bc 100644 --- a/lib/Rex/Resource/firewall/Provider/base.pm +++ b/lib/Rex/Resource/firewall/Provider/base.pm @@ -13,39 +13,8 @@ use warnings; use Data::Dumper; -sub new { - my $that = shift; - my $proto = ref($that) || $that; - my $self = {@_}; +use Moose; - bless( $self, $proto ); - - return $self; -} - -sub present { - my ( $self, $rule_config ) = @_; - die "Must be implemented by provider."; -} - -sub absent { - my ( $self, $rule_config ) = @_; - die "Must be implemented by provider."; -} - -sub enable { - my ( $self, $rule_config ) = @_; - Rex::Logger::debug("enable: Not implemented by provider."); -} - -sub disable { - my ( $self, $rule_config ) = @_; - Rex::Logger::debug("disable: Not implemented by provider."); -} - -sub logging { - my ( $self, $rule_config ) = @_; - Rex::Logger::debug("logging: Not implemented by provider."); -} +extends qw(Rex::Resource::Provider); 1; diff --git a/lib/Rex/Resource/firewall/Provider/iptables.pm b/lib/Rex/Resource/firewall/Provider/iptables.pm index bebd16fcb..33be89df1 100644 --- a/lib/Rex/Resource/firewall/Provider/iptables.pm +++ b/lib/Rex/Resource/firewall/Provider/iptables.pm @@ -11,96 +11,63 @@ use warnings; # VERSION +use Moose; + +extends qw(Rex::Resource::firewall::Provider::base); +with qw(Rex::Resource::Role::Ensureable); + use Rex::Commands::Iptables; use Rex::Helper::Run; use Data::Dumper; -use base qw(Rex::Resource::firewall::Provider::base); -sub new { - my $that = shift; - my $proto = ref($that) || $that; - my $self = $proto->SUPER::new(@_); +sub test { + my ($self) = @_; - bless( $self, $proto ); + my $rule_config = $self->config; + my @iptables_rule = $self->_build_iptables_array("A"); - return $self; + Rex::Commands::Iptables::_rule_exists( $rule_config->{ip_version}, + @iptables_rule ); } sub present { - my ( $self, $rule_config ) = @_; - - my @iptables_rule = (); + my ($self) = @_; - $rule_config->{dport} ||= $rule_config->{port}; - $rule_config->{proto} ||= 'tcp'; - $rule_config->{chain} ||= 'INPUT'; - $rule_config->{ip_version} ||= -4; + my @iptables_rule = $self->_build_iptables_array("A"); + iptables( $self->config->{ip_version}, @iptables_rule ); - if ( $rule_config->{source} - && $rule_config->{source} !~ m/\/(\d+)$/ - && $self->_version()->[0] >= 1 - && $self->_version()->[1] >= 4 ) - { - $rule_config->{source} .= "/32"; - } + return 1; +} - push( @iptables_rule, t => $rule_config->{table} ) - if ( defined $rule_config->{table} ); - push( @iptables_rule, A => uc( $rule_config->{chain} ) ) - if ( defined $rule_config->{chain} ); - push( @iptables_rule, p => $rule_config->{proto} ) - if ( defined $rule_config->{proto} ); - push( @iptables_rule, m => $rule_config->{proto} ) - if ( defined $rule_config->{proto} ); - push( @iptables_rule, s => $rule_config->{source} ) - if ( defined $rule_config->{source} ); - push( @iptables_rule, d => $rule_config->{destination} ) - if ( defined $rule_config->{destination} ); - push( @iptables_rule, sport => $rule_config->{sport} ) - if ( defined $rule_config->{sport} ); - push( @iptables_rule, dport => $rule_config->{dport} ) - if ( defined $rule_config->{dport} ); - push( @iptables_rule, "tcp-flags" => $rule_config->{tcp_flags} ) - if ( defined $rule_config->{tcp_flags} ); - push( @iptables_rule, "i" => $rule_config->{iniface} ) - if ( defined $rule_config->{iniface} ); - push( @iptables_rule, "o" => $rule_config->{outiface} ) - if ( defined $rule_config->{outiface} ); - push( @iptables_rule, "reject-with" => $rule_config->{reject_with} ) - if ( defined $rule_config->{reject_with} ); - push( @iptables_rule, "log-level" => $rule_config->{log_level} ) - if ( defined $rule_config->{log_level} ); - push( @iptables_rule, "log-prefix" => $rule_config->{log_prefix} ) - if ( defined $rule_config->{log_prefix} ); - push( @iptables_rule, "state" => $rule_config->{state} ) - if ( defined $rule_config->{state} ); - push( @iptables_rule, j => uc( $rule_config->{action} ) ) - if ( defined $rule_config->{action} ); +sub absent { + my ($self) = @_; - if ( - !Rex::Commands::Iptables::_rule_exists( - $rule_config->{ip_version}, - @iptables_rule - ) - ) - { - iptables( $rule_config->{ip_version}, @iptables_rule ); - return 1; - } + my @iptables_rule = $self->_build_iptables_array("D"); + iptables( $self->config->{ip_version}, @iptables_rule ); - return 0; + return 1; } -sub absent { - my ( $self, $rule_config ) = @_; +sub _version { + my ($self) = @_; + if ( exists $self->{__version__} ) { return $self->{__version__} } - my @iptables_rule = (); + my $version = i_run "iptables --version"; + $version =~ s/^.*\sv(\d+\.\d+\.\d+)/$1/; - $rule_config->{dport} ||= $rule_config->{port}; - $rule_config->{proto} ||= 'tcp'; - $rule_config->{chain} ||= 'INPUT'; + $self->{__version__} = [ split( /\./, $version ) ]; - $rule_config->{ip_version} ||= -4; + Rex::Logger::debug( + "Got iptables version: " . join( ", ", @{ $self->{__version__} } ) ); + + return $self->{__version__}; +} + +sub _build_iptables_array { + my ( $self, $type ) = @_; + my $rule_config = $self->config; + + my @iptables_rule = (); if ( $rule_config->{source} && $rule_config->{source} !~ m/\/(\d+)$/ @@ -112,14 +79,14 @@ sub absent { push( @iptables_rule, t => $rule_config->{table} ) if ( defined $rule_config->{table} ); - push( @iptables_rule, D => uc( $rule_config->{chain} ) ) + push( @iptables_rule, $type => uc( $rule_config->{chain} ) ) if ( defined $rule_config->{chain} ); - push( @iptables_rule, s => $rule_config->{source} ) - if ( defined $rule_config->{source} ); push( @iptables_rule, p => $rule_config->{proto} ) if ( defined $rule_config->{proto} ); push( @iptables_rule, m => $rule_config->{proto} ) if ( defined $rule_config->{proto} ); + push( @iptables_rule, s => $rule_config->{source} ) + if ( defined $rule_config->{source} ); push( @iptables_rule, d => $rule_config->{destination} ) if ( defined $rule_config->{destination} ); push( @iptables_rule, sport => $rule_config->{sport} ) @@ -143,33 +110,7 @@ sub absent { push( @iptables_rule, j => uc( $rule_config->{action} ) ) if ( defined $rule_config->{action} ); - if ( - Rex::Commands::Iptables::_rule_exists( - $rule_config->{ip_version}, - @iptables_rule - ) - ) - { - iptables( $rule_config->{ip_version}, @iptables_rule ); - return 1; - } - - return 0; -} - -sub _version { - my ($self) = @_; - if ( exists $self->{__version__} ) { return $self->{__version__} } - - my $version = i_run "iptables --version"; - $version =~ s/^.*\sv(\d+\.\d+\.\d+)/$1/; - - $self->{__version__} = [ split( /\./, $version ) ]; - - Rex::Logger::debug( - "Got iptables version: " . join( ", ", @{ $self->{__version__} } ) ); - - return $self->{__version__}; + return @iptables_rule; } 1; diff --git a/lib/Rex/Resource/firewall/Provider/ufw.pm b/lib/Rex/Resource/firewall/Provider/ufw.pm index 71769efd4..16f1c89dc 100644 --- a/lib/Rex/Resource/firewall/Provider/ufw.pm +++ b/lib/Rex/Resource/firewall/Provider/ufw.pm @@ -12,11 +12,13 @@ use warnings; # VERSION +use Moose; + use Data::Dumper; use Rex::Commands::Run; use Rex::Helper::Run; -use base qw(Rex::Resource::firewall::Provider::base); +extends qw(Rex::Resource::firewall::Provider::base); my %__action_map = ( accept => "allow", @@ -27,16 +29,6 @@ my %__action_map = ( limit => "limit", ); -sub new { - my $that = shift; - my $proto = ref($that) || $that; - my $self = $proto->SUPER::new(@_); - - bless( $self, $proto ); - - return $self; -} - sub present { my ( $self, $rule_config ) = @_; From 546a2811a84ebebd60cf2cae8ed959763c90e4ca Mon Sep 17 00:00:00 2001 From: krimdomu Date: Sun, 31 Jul 2016 00:19:22 +0200 Subject: [PATCH 4/7] more refactoring --- bin/rex | 4 +- lib/Rex.pm | 32 +- lib/Rex/CLI.pm | 10 +- lib/Rex/Commands/Task.pm | 14 +- lib/Rex/Config.pm | 21 +- lib/Rex/Controller/Base.pm | 36 ++ lib/Rex/Controller/Function.pm | 2 +- lib/Rex/Controller/Resource.pm | 20 +- lib/Rex/Controller/Task.pm | 23 + lib/Rex/Function/Common.pm | 7 +- lib/Rex/Interface/Executor.pm | 4 +- lib/Rex/Interface/Executor/Base.pm | 18 +- lib/Rex/Interface/Executor/Default.pm | 58 ++- lib/Rex/Output/Base.pm | 6 +- lib/Rex/Resource/Common.pm | 8 +- lib/Rex/RunList.pm | 13 +- lib/Rex/Shared/Var/Common.pm | 3 +- lib/Rex/Task.pm | 591 +++++++++----------------- lib/Rex/TaskList.pm | 6 +- lib/Rex/TaskList/Base.pm | 52 ++- t/param_lookup_resource.t | 1 + t/resource.t | 50 ++- 22 files changed, 437 insertions(+), 542 deletions(-) create mode 100644 lib/Rex/Controller/Base.pm create mode 100644 lib/Rex/Controller/Task.pm diff --git a/bin/rex b/bin/rex index 431fc0279..81a0fe30a 100755 --- a/bin/rex +++ b/bin/rex @@ -20,8 +20,10 @@ BEGIN { require Rex; use Rex::CLI; +use Data::Dumper; -my $rex = Rex::CLI->new; +my $app = Rex->instance; +my $rex = Rex::CLI->new(app => $app); $rex->__run__(); __END__ diff --git a/lib/Rex.pm b/lib/Rex.pm index f72b40f4c..8c1dddb08 100644 --- a/lib/Rex.pm +++ b/lib/Rex.pm @@ -76,7 +76,7 @@ use warnings; # VERSION -use Moose; +use MooseX::Singleton; # development version if this variable is not set if ( !$Rex::VERSION ) { @@ -95,6 +95,7 @@ BEGIN { use Rex::Notify; use Rex::Require; use Rex::Controller; + use Rex::Controller::Task; use Rex::Controller::Function; use Rex::Controller::Resource; use File::Basename; @@ -115,6 +116,13 @@ has feature_flags => ( isa => 'ArrayRef', ); +has run_stages => ( + is => 'ro', + isa => 'ArrayRef', + default => sub { [] }, + writer => '_set_run_stages', +); + has output => ( is => 'ro', isa => 'Rex::Output', @@ -124,13 +132,23 @@ has output => ( }, ); -my $INSTANCE; +sub push_run_stage { + my ( $self, $ctrl, $code ) = @_; + my $current = $self->run_stages; + push @{$current}, $ctrl; + + $self->_set_run_stages($current); + + $code->(); + + $current = $self->run_stages; + pop @{$current}; + $self->_set_run_stages($current); +} -# returns a singleton -sub instance { - my $class = shift; - return $INSTANCE if $INSTANCE; - $INSTANCE = $class->new(@_); +sub current_run_stage { + my ($self) = @_; + $self->run_stages->[-1]; } sub push_lib_to_inc { diff --git a/lib/Rex/CLI.pm b/lib/Rex/CLI.pm index 93300065f..b0bfdfab1 100644 --- a/lib/Rex/CLI.pm +++ b/lib/Rex/CLI.pm @@ -13,8 +13,6 @@ use warnings; use Moose; -extends qw(Rex); - use FindBin; use File::Basename qw(basename dirname); use Time::HiRes qw(gettimeofday tv_interval); @@ -56,13 +54,19 @@ if ( $#ARGV < 0 ) { @ARGV = qw(-h); } +has app => ( + is => 'ro', + isa => 'Rex', +); + has run_list => ( is => 'ro', isa => 'Rex::RunList', lazy => 1, default => sub { my $self = shift; - Rex::RunList->instance( app => $self ); + Rex::RunList->app( Rex->instance ); + Rex::RunList->instance; } ); diff --git a/lib/Rex/Commands/Task.pm b/lib/Rex/Commands/Task.pm index 1f9571f3f..d48c9a851 100644 --- a/lib/Rex/Commands/Task.pm +++ b/lib/Rex/Commands/Task.pm @@ -31,6 +31,7 @@ use warnings; # VERSION require Rex::Exporter; +use Data::Dumper; use vars qw(@EXPORT); use base qw(Rex::Exporter); @@ -176,7 +177,8 @@ sub task { use warnings; $options->{'dont_register'} ||= $dont_register_tasks; - my $task_o = Rex::TaskList->create()->create_task( $task_name, @_, $options ); + my $task_o = Rex::TaskList->create( app => Rex->instance ) + ->create_task( $task_name, @_, $options ); if (!$class->can($task_name_save) && $task_name_save =~ m/^[a-zA-Z_][a-zA-Z0-9_]+$/ ) @@ -188,15 +190,7 @@ sub task { Rex::Logger::info("Running task $task_name on current connection"); my $param; - if ( scalar @_ == 1 && ref $_[0] eq "HASH" ) { - $param = $_[0]; - } - elsif ( $REGISTER_SUB_HASH_PARAMETER && scalar @_ % 2 == 0 ) { - $param = {@_}; - } - else { - $param = \@_; - } + $param = {@_}; $task_o->run( "", params => $param ); }; diff --git a/lib/Rex/Config.pm b/lib/Rex/Config.pm index bfe736018..9c0685ec9 100644 --- a/lib/Rex/Config.pm +++ b/lib/Rex/Config.pm @@ -795,12 +795,7 @@ sub get_template_function { return sub { my ( $content, $template_vars ) = @_; $template_vars = { - Rex::Commands::Task::task()->get_opts, - ( - Rex::Resource->is_inside_resource - ? %{ Rex::Resource->get_current_resource()->get_all_parameters } - : () - ), + %{ Rex->instance->current_run_stage->params }, %{ $template_vars || {} } } if ( Rex::Commands::Task::task() ); @@ -814,12 +809,7 @@ sub get_template_function { return sub { my ( $content, $template_vars ) = @_; $template_vars = { - Rex::Commands::Task::task()->get_opts, - ( - Rex::Resource->is_inside_resource - ? %{ Rex::Resource->get_current_resource()->get_all_parameters } - : () - ), + %{ Rex->instance->current_run_stage->params }, %{ $template_vars || {} } } if ( Rex::Commands::Task::task() ); @@ -832,12 +822,7 @@ sub get_template_function { return sub { my ( $content, $template_vars ) = @_; $template_vars = { - Rex::Commands::Task::task()->get_opts, - ( - Rex::Resource->is_inside_resource - ? %{ Rex::Resource->get_current_resource()->get_all_parameters } - : () - ), + %{ Rex->instance->current_run_stage->params }, %{ $template_vars || {} } } if ( Rex::Commands::Task::task() ); diff --git a/lib/Rex/Controller/Base.pm b/lib/Rex/Controller/Base.pm new file mode 100644 index 000000000..11acde952 --- /dev/null +++ b/lib/Rex/Controller/Base.pm @@ -0,0 +1,36 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::Controller::Base; + +use strict; +use warnings; + +# VERSION + +use Moose; + +extends qw(Rex::Controller); + +has params => ( + is => 'ro', + isa => 'HashRef', + writer => '_set_params', +); + +sub param { + my ( $self, $what ) = @_; + return $self->params->{$what} if ($what); +} + +sub set_param { + my ( $self, $what, $value ) = @_; + my $p = $self->params; + $p->{$what} = $value; + $self->_set_params($p); +} + +1; diff --git a/lib/Rex/Controller/Function.pm b/lib/Rex/Controller/Function.pm index 75ed4a817..245c1a350 100644 --- a/lib/Rex/Controller/Function.pm +++ b/lib/Rex/Controller/Function.pm @@ -13,6 +13,6 @@ use warnings; use Moose; -extends qw(Rex::Controller::Resource); +extends qw(Rex::Controller::Base); 1; diff --git a/lib/Rex/Controller/Resource.pm b/lib/Rex/Controller/Resource.pm index 866a8539b..96d0aa633 100644 --- a/lib/Rex/Controller/Resource.pm +++ b/lib/Rex/Controller/Resource.pm @@ -13,24 +13,6 @@ use warnings; use Moose; -extends qw(Rex::Controller); - -has params => ( - is => 'ro', - isa => 'HashRef', - writer => '_set_params', -); - -sub param { - my ( $self, $what ) = @_; - return $self->params->{$what} if ($what); -} - -sub set_param { - my ( $self, $what, $value ) = @_; - my $p = $self->params; - $p->{$what} = $value; - $self->_set_params($p); -} +extends qw(Rex::Controller::Base); 1; diff --git a/lib/Rex/Controller/Task.pm b/lib/Rex/Controller/Task.pm new file mode 100644 index 000000000..a271cc4d3 --- /dev/null +++ b/lib/Rex/Controller/Task.pm @@ -0,0 +1,23 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::Controller::Task; + +use strict; +use warnings; + +# VERSION + +use Moose; + +extends qw(Rex::Controller::Base); + +has task => ( + is => 'ro', + isa => 'Rex::Task', +); + +1; diff --git a/lib/Rex/Function/Common.pm b/lib/Rex/Function/Common.pm index 8b4bbd516..568bc36b8 100644 --- a/lib/Rex/Function/Common.pm +++ b/lib/Rex/Function/Common.pm @@ -95,7 +95,12 @@ sub function { # * unless # * creates - $ret = $f->{code}->( $c, @args, %arg_options ); + $app->push_run_stage( + $c, + sub { + $ret = $f->{code}->( $c, @args, %arg_options ); + } + ); last; } if ( !$found ) { diff --git a/lib/Rex/Interface/Executor.pm b/lib/Rex/Interface/Executor.pm index 63b78122a..f70aca4df 100644 --- a/lib/Rex/Interface/Executor.pm +++ b/lib/Rex/Interface/Executor.pm @@ -14,7 +14,7 @@ use warnings; use Data::Dumper; sub create { - my ( $class, $type ) = @_; + my ( $class, $type, @params ) = @_; unless ($type) { $type = "Default"; @@ -24,7 +24,7 @@ sub create { eval "use $class_name;"; if ($@) { die("Error loading file interface $type.\n$@"); } - return $class_name->new; + return $class_name->new(@params); } diff --git a/lib/Rex/Interface/Executor/Base.pm b/lib/Rex/Interface/Executor/Base.pm index 72307169c..f08a62980 100644 --- a/lib/Rex/Interface/Executor/Base.pm +++ b/lib/Rex/Interface/Executor/Base.pm @@ -11,17 +11,17 @@ use warnings; # VERSION -sub new { - my $that = shift; - my $proto = ref($that) || $that; - my $self = $proto->SUPER::new(@_); +use Moose; - bless( $self, $proto ); +has app => ( + is => 'ro', + isa => 'Rex', +); - return $self; -} - -sub exec { die("Should be implemented by interface class."); } +has task => ( + is => 'ro', + isa => 'Rex::Task' +); sub set_task { my ( $self, $task ) = @_; diff --git a/lib/Rex/Interface/Executor/Default.pm b/lib/Rex/Interface/Executor/Default.pm index a5e2ea825..d52193365 100644 --- a/lib/Rex/Interface/Executor/Default.pm +++ b/lib/Rex/Interface/Executor/Default.pm @@ -16,24 +16,16 @@ use Rex::Logger; use Data::Dumper; use Rex::Interface::Executor::Base; -use base qw(Rex::Interface::Executor::Base); require Rex::Args; -sub new { - my $that = shift; - my $proto = ref($that) || $that; - my $self = {@_}; - - bless( $self, $proto ); - - return $self; -} +use Moose; +extends qw(Rex::Interface::Executor::Base); sub exec { my ( $self, $opts, $args ) = @_; - my $task = $self->{task}; + my $task = $self->task; Rex::Logger::debug( "Executing " . $task->name ); @@ -43,26 +35,32 @@ sub exec { eval { my $code = $task->code; - Rex::Hook::run_hook( task => "before_execute", $task->name, @_ ); - - if ($wantarray) { - if ( ref $opts eq "ARRAY" ) { - @ret = $code->( @{$opts} ); - } - else { - @ret = $code->( $opts, $args ); + my $c = Rex::Controller::Task->new( app => $self->app, params => $opts ); + $self->app->push_run_stage( + $c, + sub { + Rex::Hook::run_hook( task => "before_execute", $task->name, @_ ); + + if ($wantarray) { + if ( ref $opts eq "ARRAY" ) { + @ret = $code->( $self->app->current_run_stage ); + } + else { + @ret = $code->( $self->app->current_run_stage ); + } + } + else { + if ( ref $opts eq "ARRAY" ) { + $ret[0] = $code->( $self->app->current_run_stage ); + } + else { + $ret[0] = $code->( $self->app->current_run_stage ); + } + } + + Rex::Hook::run_hook( task => "after_execute", $task->name, @_ ); } - } - else { - if ( ref $opts eq "ARRAY" ) { - $ret[0] = $code->( @{$opts} ); - } - else { - $ret[0] = $code->( $opts, $args ); - } - } - - Rex::Hook::run_hook( task => "after_execute", $task->name, @_ ); + ); }; my $error = $@; diff --git a/lib/Rex/Output/Base.pm b/lib/Rex/Output/Base.pm index 783cd9806..0509cdad2 100644 --- a/lib/Rex/Output/Base.pm +++ b/lib/Rex/Output/Base.pm @@ -17,17 +17,17 @@ extends qw(Rex::Output); sub print_s { my ( $self, $data ) = @_; - print "[$data->{title}] $data->{msg} "; + print "[$data->{title}] $data->{msg} " if !$::QUIET; } sub endln_ok { my ($self) = @_; - print "done.\n"; + print "done.\n" if !$::QUIET; } sub endln_failed { my ($self) = @_; - print "failed.\n"; + print "failed.\n" if !$::QUIET; } 1; diff --git a/lib/Rex/Resource/Common.pm b/lib/Rex/Resource/Common.pm index 9a016654c..0e658904b 100644 --- a/lib/Rex/Resource/Common.pm +++ b/lib/Rex/Resource/Common.pm @@ -159,7 +159,13 @@ sub resource { ); my $c = Rex::Controller::Resource->new( app => $app, params => \%args ); - $res->call( $c, @_ ); + my @args = @_; + $app->push_run_stage( + $c, + sub { + $res->call( $c, @args ); + } + ); last; } if ( !$found ) { diff --git a/lib/Rex/RunList.pm b/lib/Rex/RunList.pm index 08c1ded53..769edc418 100644 --- a/lib/Rex/RunList.pm +++ b/lib/Rex/RunList.pm @@ -14,22 +14,13 @@ use Rex::TaskList; # VERSION -use Moose; - -my $INSTANCE; +use MooseX::Singleton; has app => ( - is => 'ro', + is => 'rw', isa => 'Rex', ); -# returns a singleton -sub instance { - my $class = shift; - return $INSTANCE if $INSTANCE; - $INSTANCE = $class->new(@_); -} - sub add_task { my ( $self, $task_name, $task_args, $task_opts ) = @_; $task_args ||= []; diff --git a/lib/Rex/Shared/Var/Common.pm b/lib/Rex/Shared/Var/Common.pm index 1332ad8cd..2a265a405 100644 --- a/lib/Rex/Shared/Var/Common.pm +++ b/lib/Rex/Shared/Var/Common.pm @@ -17,6 +17,7 @@ our @EXPORT_OK = qw/__lock __store __retrieve/; use Fcntl qw(:DEFAULT :flock); use Storable; +use Carp; # $PARENT_PID gets set when Rex starts. This value remains the same after the # process forks. So $PARENT_PID is always the pid of the parent process. $$ @@ -38,7 +39,7 @@ sub __lock { sub __store { my $ref = shift; - store( $ref, $FILE ); + eval { store( $ref, $FILE ); 1; } or do { confess $@ }; } sub __retrieve { diff --git a/lib/Rex/Task.pm b/lib/Rex/Task.pm index 3c7da2b19..b991c1d81 100644 --- a/lib/Rex/Task.pm +++ b/lib/Rex/Task.pm @@ -58,100 +58,82 @@ require Rex::Commands; require Rex::Args; -=head2 new - -This is the constructor. - - $task = Rex::Task->new( - func => sub { some_code_here }, - server => [ @server ], - desc => $description, - no_ssh => $no_ssh, - hidden => $hidden, - auth => { - user => $user, - password => $password, - private_key => $private_key, - public_key => $public_key, - }, - before => [sub {}, sub {}, ...], - after => [sub {}, sub {}, ...], - around => [sub {}, sub {}, ...], - before_task_start => [sub {}, sub {}, ...], - after_task_finished => [sub {}, sub {}, ...], - name => $task_name, - executor => Rex::Interface::Executor->create, - opts => {key1 => val1, key2 => val2, ...}, - args => [arg1, arg2, ...], - ); - -=cut - -sub new { - my $that = shift; - my $proto = ref($that) || $that; - my $self = {@_}; - - bless( $self, $proto ); - - if ( !exists $self->{name} ) { - die("You have to define a task name."); - } - - $self->{no_ssh} ||= 0; - $self->{func} ||= sub { }; - $self->{executor} ||= Rex::Interface::Executor->create; - $self->{opts} ||= {}; - $self->{args} ||= []; - - $self->{connection} = undef; - - # set to true as default - if ( !exists $self->{exit_on_connect_fail} ) { - $self->{exit_on_connect_fail} = 1; - } - - return $self; -} - -=head2 connection - -Returns the current connection object. - -=cut - -sub connection { - my ($self) = @_; - if ( !exists $self->{connection} || !$self->{connection} ) { - $self->{connection} = - Rex::Interface::Connection->create( $self->get_connection_type ); - } - - $self->{connection}; -} - -=head2 executor - -Returns the current executor object. - -=cut - -sub executor { - my ($self) = @_; - $self->{executor}->set_task($self); - return $self->{executor}; -} - -=head2 hidden - -Returns true if the task is hidden. (Should not be displayed on ,,rex -T''.) - -=cut - -sub hidden { - my ($self) = @_; - return $self->{hidden}; -} +use Moose; +use MooseX::Aliases; + +has name => ( is => 'ro', isa => 'Str' ); +has func => ( + is => 'ro', + isa => 'CodeRef', + default => sub { + sub { } + } +); +alias code => 'func'; + +has server => ( is => 'ro', isa => 'ArrayRef | Undef' ); +has desc => ( is => 'ro', isa => 'Str | Undef' ); +has parallelism => ( is => 'rw', isa => 'Int', default => sub { 1 } ); +has no_ssh => ( is => 'ro', isa => 'Bool', default => sub { 0 } ); +has hidden => ( is => 'ro', isa => 'Bool | Undef', default => sub { 0 } ); +has auth => ( is => 'ro', isa => 'HashRef | Undef', default => sub { } ); +has before_hooks => + ( is => 'ro', isa => 'ArrayRef[CodeRef] | Undef', default => sub { } ); +has after_hooks => + ( is => 'ro', isa => 'ArrayRef[CodeRef] | Undef', default => sub { } ); +has around_hooks => + ( is => 'ro', isa => 'ArrayRef[CodeRef] | Undef', default => sub { } ); +has before_task_start_hooks => + ( is => 'ro', isa => 'ArrayRef[CodeRef] | Undef', default => sub { } ); +has after_task_finished_hooks => + ( is => 'ro', isa => 'ArrayRef[CodeRef] | Undef', default => sub { } ); +has app => ( is => 'ro', isa => 'Rex', default => sub { Rex->instance } ); +has executor => ( + is => 'ro', + isa => 'Rex::Interface::Executor::Base', + lazy => 1, + default => sub { + my $self = shift; + my $exec = Rex::Interface::Executor->create( + "Default", + app => $self->app, + task => $self + ); + return $exec; + } +); +has opts => ( + is => 'ro', + isa => 'HashRef | Undef', + default => sub { {} }, + writer => '_set_opts' +); +has args => ( + is => 'ro', + isa => 'ArrayRef | Undef', + default => sub { [] }, + writer => '_set_args' +); +has connection => ( + is => 'ro', + isa => 'Object | Undef', + lazy => 1, + clearer => '_clear_connection', + default => sub { + my $self = shift; + Rex::Interface::Connection->create( $self->get_connection_type ); + } +); +has exit_on_connect_fail => ( is => 'ro', isa => 'Bool', default => sub { 1 } ); +has connection_type => ( is => 'ro', isa => 'Str', required => 0, ); +has current_server => ( + is => 'ro', + + #isa => 'Rex::Group::Entry::Server | Undef | Str', + writer => '_set_current_server', + lazy => 1, + default => sub { "" } +); =head2 server @@ -159,114 +141,57 @@ Returns the servers on which the task should be executed as an ArrayRef. =cut -sub server { - my ($self) = @_; - - my @server = @{ $self->{server} }; - my @ret = (); - - if ( ref( $server[-1] ) eq "HASH" ) { - Rex::deprecated( - undef, "0.40", - "Defining extra credentials within the task creation is deprecated.", - "Please use set auth => task => 'taskname' instead." - ); - - # use extra defined credentials - my $data = pop(@server); - $self->set_auth( "user", $data->{'user'} ); - $self->set_auth( "password", $data->{'password'} ); - - if ( exists $data->{"private_key"} ) { - $self->set_auth( "private_key", $data->{"private_key"} ); - $self->set_auth( "public_key", $data->{"public_key"} ); - } - } - - if ( ref( $self->{server} ) eq "ARRAY" - && scalar( @{ $self->{server} } ) > 0 ) - { - for my $srv ( @{ $self->{server} } ) { - if ( ref($srv) eq "CODE" ) { - push( @ret, &$srv() ); - } - else { - if ( ref $srv && $srv->isa("Rex::Group::Entry::Server") ) { - push( @ret, $srv->get_servers ); - } - else { - push( @ret, $srv ); - } - } - } - } - elsif ( ref( $self->{server} ) eq "CODE" ) { - push( @ret, &{ $self->{server} }() ); - } - else { - push( @ret, Rex::Group::Entry::Server->new( name => "" ) ); - } - - return [@ret]; -} - -=head2 set_server(@server) - -With this method you can set new servers on which the task should be executed on. - -=cut - -sub set_server { - my ( $self, @server ) = @_; - $self->{server} = \@server; -} - -=head2 delete_server - -Delete every server registered to the task. - -=cut - -sub delete_server { - my ($self) = @_; - delete $self->{current_server}; - delete $self->{server}; - $self->rethink_connection; -} - -=head2 current_server - -Returns the current server on which the tasks gets executed right now. - -=cut - -sub current_server { - my ($self) = @_; - return $self->{current_server} - || Rex::Group::Entry::Server->new( name => "" ); -} - -=head2 desc - -Returns the description of a task. - -=cut - -sub desc { - my ($self) = @_; - return $self->{desc}; -} - -=head2 set_desc($description) - -Set the description of a task. - -=cut - -sub set_desc { - my ( $self, $desc ) = @_; - $self->{desc} = $desc; -} +# TODO coerce server attribute to objects +#sub server { +# my ($self) = @_; +# +# my @server = @{ $self->{server} }; +# my @ret = (); +# +# if ( ref( $server[-1] ) eq "HASH" ) { +# Rex::deprecated( +# undef, "0.40", +# "Defining extra credentials within the task creation is deprecated.", +# "Please use set auth => task => 'taskname' instead." +# ); +# +# # use extra defined credentials +# my $data = pop(@server); +# $self->set_auth( "user", $data->{'user'} ); +# $self->set_auth( "password", $data->{'password'} ); +# +# if ( exists $data->{"private_key"} ) { +# $self->set_auth( "private_key", $data->{"private_key"} ); +# $self->set_auth( "public_key", $data->{"public_key"} ); +# } +# } +# +# if ( ref( $self->{server} ) eq "ARRAY" +# && scalar( @{ $self->{server} } ) > 0 ) +# { +# for my $srv ( @{ $self->{server} } ) { +# if ( ref($srv) eq "CODE" ) { +# push( @ret, &$srv() ); +# } +# else { +# if ( ref $srv && $srv->isa("Rex::Group::Entry::Server") ) { +# push( @ret, $srv->get_servers ); +# } +# else { +# push( @ret, $srv ); +# } +# } +# } +# } +# elsif ( ref( $self->{server} ) eq "CODE" ) { +# push( @ret, &{ $self->{server} }() ); +# } +# else { +# push( @ret, Rex::Group::Entry::Server->new( name => "" ) ); +# } +# +# return [@ret]; +#} =head2 is_remote @@ -282,7 +207,7 @@ sub is_remote { } } else { - if ( exists $self->{server} && scalar( @{ $self->{server} } ) > 0 ) { + if ( $self->server && scalar( @{ $self->server } ) > 0 ) { return 1; } } @@ -309,8 +234,7 @@ Returns true (1) if the task gets executed over http protocol. sub is_http { my ($self) = @_; - return ( $self->{"connection_type"} - && lc( $self->{"connection_type"} ) eq "http" ); + return ( $self->connection_type && lc( $self->connection_type ) eq "http" ); } =head2 is_https @@ -321,8 +245,7 @@ Returns true (1) if the task gets executed over https protocol. sub is_https { my ($self) = @_; - return ( $self->{"connection_type"} - && lc( $self->{"connection_type"} ) eq "https" ); + return ( $self->connection_type && lc( $self->connection_type ) eq "https" ); } =head2 is_openssh @@ -333,8 +256,8 @@ Returns true (1) if the task gets executed with openssh. sub is_openssh { my ($self) = @_; - return ( $self->{"connection_type"} - && lc( $self->{"connection_type"} ) eq "openssh" ); + return ( $self->connection_type + && lc( $self->connection_type ) eq "openssh" ); } =head2 want_connect @@ -403,18 +326,19 @@ With this method you can modify values of the task. =cut -sub modify { - my ( $self, $key, $value ) = @_; - - if ( ref( $self->{$key} ) eq "ARRAY" ) { - push( @{ $self->{$key} }, $value ); - } - else { - $self->{$key} = $value; - } - - $self->rethink_connection; -} +# TODO use Moose writer functions +#sub modify { +# my ( $self, $key, $value ) = @_; +# +# if ( ref( $self->{$key} ) eq "ARRAY" ) { +# push( @{ $self->{$key} }, $value ); +# } +# else { +# $self->{$key} = $value; +# } +# +# $self->rethink_connection; +#} =head2 rethink_connection @@ -424,7 +348,7 @@ Deletes current connection object. sub rethink_connection { my ($self) = @_; - delete $self->{connection}; + $self->_clear_connection; } =head2 user @@ -435,8 +359,8 @@ Returns the username the task will use. sub user { my ($self) = @_; - if ( exists $self->{auth} && $self->{auth}->{user} ) { - return $self->{auth}->{user}; + if ( $self->auth && $self->auth->{user} ) { + return $self->auth->{user}; } } @@ -448,7 +372,9 @@ Set the username of a task. sub set_user { my ( $self, $user ) = @_; - $self->{auth}->{user} = $user; + my $auth = $self->auth; + $auth->{user} = $user; + $self->_set_auth($auth); } =head2 password @@ -459,8 +385,8 @@ Returns the password that will be used. sub password { my ($self) = @_; - if ( exists $self->{auth} && $self->{auth}->{password} ) { - return $self->{auth}->{password}; + if ( $self->auth && $self->auth->{password} ) { + return $self->auth->{password}; } } @@ -472,40 +398,9 @@ Set the password of the task. sub set_password { my ( $self, $password ) = @_; - $self->{auth}->{password} = $password; -} - -=head2 name - -Returns the name of the task. - -=cut - -sub name { - my ($self) = @_; - return $self->{name}; -} - -=head2 code - -Returns the code of the task. - -=cut - -sub code { - my ($self) = @_; - return $self->{func}; -} - -=head2 set_code(\&code_ref) - -Set the code of the task. - -=cut - -sub set_code { - my ( $self, $code ) = @_; - $self->{func} = $code; + my $auth = $self->auth; + $auth->{password} = $password; + $self->_set_auth($auth); } =head2 run_hook($server, $hook) @@ -550,10 +445,12 @@ sub set_auth { if ( scalar(@_) > 3 ) { my $_d = shift; - $self->{auth} = {@_}; + $self->_set_auth( {@_} ); } else { - $self->{auth}->{$key} = $value; + my $auth = $self->auth; + $auth->{$key} = $value; + $self->_set_auth($auth); } } @@ -569,7 +466,7 @@ sub merge_auth { # merge auth hashs # auth info of task has precedence - my %auth = $server->merge_auth( $self->{auth} ); + my %auth = $server->merge_auth( $self->auth ); return \%auth; } @@ -584,33 +481,11 @@ sub get_sudo_password { my ($self) = @_; my $server = $self->connection->server; - my %auth = $server->merge_auth( $self->{auth} ); + my %auth = $server->merge_auth( $self->auth ); return $auth{sudo_password}; } -=head2 parallelism - -Get the parallelism count of a task. - -=cut - -sub parallelism { - my ($self) = @_; - return $self->{parallelism}; -} - -=head2 set_parallelism($count) - -Set the parallelism of the task. - -=cut - -sub set_parallelism { - my ( $self, $para ) = @_; - $self->{parallelism} = $para; -} - =head2 connect($server) Initiate the connection to $server. @@ -623,7 +498,7 @@ sub connect { if ( !ref $server ) { $server = Rex::Group::Entry::Server->new( name => $server ); } - $self->{current_server} = $server; + $self->_set_current_server($server); # need to be called, in case of a run_task task call. # see #788 @@ -671,14 +546,15 @@ sub connect { ssh => $self->connection->get_connection_object, server => $server, cache => Rex::Interface::Cache->create(), - task => [], + task => [$self], profiler => $profiler, reporter => Rex::Report->create( Rex::Config->get_report_type ), notify => Rex::Notify->new(), } ); - push @{ Rex::get_current_connection()->{task} }, $self; + # TODO ist nach oben gewandert + # push @{ Rex::get_current_connection()->{task} }, $self; $profiler->start("connect"); eval { @@ -747,7 +623,7 @@ sub disconnect { Rex::Commands::profiler()->report; } - delete $self->{connection}; + $self->_clear_connection; $self->run_hook( \$server, "after" ); @@ -765,22 +641,21 @@ Dump task data. sub get_data { my ($self) = @_; - return { - func => $self->{func}, - server => $self->{server}, - desc => $self->{desc}, - no_ssh => $self->{no_ssh}, - hidden => $self->{hidden}, - auth => $self->{auth}, - before => $self->{before}, - after => $self->{after}, - around => $self->{around}, - name => $self->{name}, - executor => $self->{executor}, - connection_type => $self->{connection_type}, - opts => $self->{opts}, - args => $self->{args}, + func => $self->func, + server => $self->server, + desc => $self->desc, + no_ssh => $self->no_ssh, + hidden => $self->hidden, + auth => $self->auth, + before_hooks => $self->before_hooks, + after_hooks => $self->after_hooks, + around_hooks => $self->around_hooks, + name => $self->name, + executor => $self->executor, + connection_type => $self->connection_type, + opts => $self->opts, + args => $self->args, }; } @@ -791,12 +666,10 @@ Run the task on C<$server>, with C<%options>. =cut sub run { - return pre_40_run(@_) unless ref $_[0]; - my ( $self, $server, %options ) = @_; - $options{opts} ||= { $self->get_opts }; - $options{args} ||= [ $self->get_args ]; + $options{opts} ||= $self->opts; + $options{args} ||= $self->args; $options{params} ||= $options{opts}; if ( !ref $server ) { @@ -862,7 +735,7 @@ sub run { my $wantarray = wantarray; eval { - $self->set_opts( %{ $options{params} } ) + $self->_set_opts( $options{params} ) if ref $options{params} eq "HASH"; if ($wantarray) { @ret = $self->executor->exec( $options{params}, $options{args} ); @@ -928,88 +801,20 @@ sub run { } } -sub pre_40_run { - my ( $class, $task_name, $server_overwrite, $params ) = @_; - - # static calls to this method are deprecated - Rex::deprecated( "Rex::Task->run()", "0.40" ); - - my $tasklist = Rex::TaskList->create; - my $task = $tasklist->get_task($task_name); - - $task->set_server($server_overwrite) if $server_overwrite; - $tasklist->run( $task, params => $params ); -} - =head2 modify_task($task, $key => $value) Modify C<$task>, by setting C<$key> to C<$value>. =cut -sub modify_task { - my $class = shift; - my $task = shift; - my $key = shift; - my $value = shift; - - Rex::TaskList->create()->get_task($task)->modify( $key => $value ); -} - -=head2 is_task - -Returns true(1) if the passed object is a task. - -=cut - -sub is_task { - my ( $class, $task ) = @_; - return Rex::TaskList->create()->is_task($task); -} - -=head2 get_tasks - -Returns list of tasks. - -=cut - -sub get_tasks { - my ( $class, @tmp ) = @_; - return Rex::TaskList->create()->get_tasks(@tmp); -} - -=head2 get_desc - -Returns description of task. - -=cut - -sub get_desc { - my ( $class, @tmp ) = @_; - return Rex::TaskList->create()->get_desc(@tmp); -} - -=head2 exit_on_connect_fail - -Returns true if rex should exit on connect failure. - -=cut - -sub exit_on_connect_fail { - my ($self) = @_; - return $self->{exit_on_connect_fail}; -} - -=head2 set_exit_on_connect_fail - -Sets if rex should exit on connect failure. - -=cut - -sub set_exit_on_connect_fail { - my ( $self, $exit ) = @_; - $self->{exit_on_connect_fail} = $exit; -} +#sub modify_task { +# my $class = shift; +# my $task = shift; +# my $key = shift; +# my $value = shift; +# +# Rex::TaskList->create()->get_task($task)->modify( $key => $value ); +#} =head2 get_args @@ -1019,7 +824,7 @@ Returns arguments of task. sub get_args { my ($self) = @_; - @{ $self->{args} || [] }; + @{ $self->args || [] }; } =head2 get_opts @@ -1030,7 +835,7 @@ Returns options of task. sub get_opts { my ($self) = @_; - %{ $self->{opts} || {} }; + %{ $self->opts || {} }; } =head2 set_args @@ -1041,7 +846,7 @@ Sets arguments for task. sub set_args { my ( $self, @args ) = @_; - $self->{args} = \@args; + $self->_set_args( \@args ); } =head2 set_opt @@ -1052,7 +857,9 @@ Sets an option for task. sub set_opt { my ( $self, $key, $value ) = @_; - $self->{opts}->{$key} = $value; + my $opts = $self->opts; + $opts->{$key} = $value; + $self->_set_opts($opts); } =head2 set_opts @@ -1063,7 +870,7 @@ Sets options for task. sub set_opts { my ( $self, %opts ) = @_; - $self->{opts} = \%opts; + $self->_set_opts( \%opts ); } =head2 clone diff --git a/lib/Rex/TaskList.pm b/lib/Rex/TaskList.pm index 3866dac57..13367bae4 100644 --- a/lib/Rex/TaskList.pm +++ b/lib/Rex/TaskList.pm @@ -22,7 +22,9 @@ use vars qw(%tasks); our $task_list = {}; sub create { - my ($class) = @_; + my ( $class, %params ) = @_; + + $params{app} ||= Rex->instance; # create only one object if ( ref($task_list) =~ m/^Rex::TaskList::/ ) { @@ -41,7 +43,7 @@ sub create { die("TaskList module not found: $@"); } - $task_list = $class_name->new; + $task_list = $class_name->new(%params); Rex::Logger::debug( "new distribution class of type " . ref($task_list) . " created." ); diff --git a/lib/Rex/TaskList/Base.pm b/lib/Rex/TaskList/Base.pm index 989fb230f..43f70ff05 100644 --- a/lib/Rex/TaskList/Base.pm +++ b/lib/Rex/TaskList/Base.pm @@ -11,6 +11,10 @@ use warnings; # VERSION +use Moose; + +has app => ( is => 'ro', isa => 'Rex' ); + BEGIN { use Rex::Shared::Var; share qw(@SUMMARY); @@ -29,18 +33,12 @@ use POSIX qw(floor); require Rex::Commands::Environment; -sub new { - my $that = shift; - my $proto = ref($that) || $that; - my $self = {@_}; - - bless( $self, $proto ); +sub BUILD { + my $self = shift; $self->{IN_TRANSACTION} = 0; $self->{DEFAULT_AUTH} = 1; $self->{tasks} = {}; - - return $self; } sub create_task { @@ -208,14 +206,16 @@ sub create_task { ? $options->{exit_on_connect_fail} : 1 ), - before => [], - after => [], - around => [], - after_task_finished => [], - before_task_start => [], - name => $task_name, - executor => Rex::Interface::Executor->create, - connection_type => Rex::Config->get_connection_type, + before_hooks => [], + after_hooks => [], + around_hooks => [], + after_task_finished_hooks => [], + before_task_start_hooks => [], + name => $task_name, + + # executor => Rex::Interface::Executor->create, + app => $self->app, + connection_type => Rex::Config->get_connection_type, ); if ( $self->{DEFAULT_AUTH} ) { @@ -351,13 +351,19 @@ sub build_child_coderef { my $e = $@; my $exit_code = $@ ? ( $? || 1 ) : 0; - push @SUMMARY, - { - task => $task->name, - server => $server->to_s, - exit_code => $exit_code, - error_message => $e, - }; + eval { + push @SUMMARY, + { + task => $task->name, + server => $server->to_s, + exit_code => $exit_code, + error_message => $e, + }; + 1; + } or do { + die $e; + }; + } Rex::Logger::debug("Destroying all cached os information"); diff --git a/t/param_lookup_resource.t b/t/param_lookup_resource.t index 009830333..746fdf763 100644 --- a/t/param_lookup_resource.t +++ b/t/param_lookup_resource.t @@ -3,6 +3,7 @@ package MyTest; use Rex -base; use Rex::Resource::Common; use Test::More; +use Rex::Output; $::QUIET = 1; diff --git a/t/resource.t b/t/resource.t index 909b5e16d..c502717c3 100644 --- a/t/resource.t +++ b/t/resource.t @@ -2,14 +2,27 @@ use Test::More tests => 11; use Rex -base; use Rex::Resource; use Rex::Resource::Common; +use Rex::Output; + +use Data::Dumper; $::QUIET = 1; resource( "testres", + { + params_list => [ + name => { + isa => 'Str', + default => sub { shift } + }, + file => { isa => 'Str', default => "/etc/passwd", }, + ], + }, sub { - my $name = resource_name; - my $file = param_lookup "file", "/etc/passwd"; + my ($c) = @_; + my $name = $c->param("name"); + my $file = $c->param("file"); is( $name, "foo", "testres name is foo" ); is( $file, "/etc/passwd", "testres got default file param" ); @@ -23,9 +36,19 @@ resource( resource( "testres2", + { + params_list => [ + name => { + isa => 'Str', + default => sub { shift } + }, + file => { isa => 'Str', }, + ], + }, sub { - my $name = resource_name; - my $file = param_lookup "file", "/etc/passwd"; + my ($c) = @_; + my $name = $c->param("name"); + my $file = $c->param("file"); is( $name, "bar", "testres2 name is bar" ); is( $file, "/etc/shadow", "testres2 got custom param" ); @@ -46,9 +69,19 @@ resource( resource( "testres3", + { + params_list => [ + name => { + isa => 'Str', + default => sub { shift } + }, + file => { isa => 'Str', }, + ], + }, sub { - my $name = resource_name; - my $file = param_lookup "file", "/etc/passwd"; + my ($c) = @_; + my $name = $c->param("name"); + my $file = $c->param("file"); is( $name, "baz", "testres3 name is baz" ); is( $file, "/etc/foo", "testres3 got custom param" ); @@ -61,7 +94,8 @@ resource( task( "test1", sub { - my $file = param_lookup "file", "/etc/groups"; + my $c = shift; + my $file = $c->param("file") || "/etc/securetty"; testres("foo"); testres2( "bar", file => "/etc/shadow" ); @@ -71,7 +105,7 @@ task( } ); -test1( { file => "/etc/securetty" } ); +test1( file => "/etc/securetty" ); done_testing(); From 3b134ac8f84142b1e8bfe5a8bbfdd5f11c18e111 Mon Sep 17 00:00:00 2001 From: krimdomu Date: Thu, 4 Aug 2016 21:15:26 +0200 Subject: [PATCH 5/7] added multisub, migrated firewall resource to use multisub, migrated run function to use multisub --- lib/Rex.pm | 30 +--- lib/Rex/Controller.pm | 35 ----- lib/Rex/Controller/Base.pm | 36 ----- lib/Rex/Controller/Function.pm | 18 --- lib/Rex/Controller/Resource.pm | 18 --- lib/Rex/Controller/Task.pm | 23 ---- lib/Rex/DSL/Common.pm | 27 ++++ lib/Rex/Function/Common.pm | 138 ++----------------- lib/Rex/Function/run.pm | 7 +- lib/Rex/Interface/Executor/Default.pm | 44 +++--- lib/Rex/MultiSub.pm | 115 ++++++++++++++++ lib/Rex/MultiSub/Function.pm | 54 ++++++++ lib/Rex/MultiSub/LookupTable.pm | 31 +++++ lib/Rex/MultiSub/PosValidatedList.pm | 47 +++++++ lib/Rex/MultiSub/Resource.pm | 63 +++++++++ lib/Rex/MultiSub/ValidatedHash.pm | 52 +++++++ lib/Rex/Resource/Common.pm | 190 +------------------------- lib/Rex/Resource/Provider.pm | 12 ++ lib/Rex/Resource/firewall.pm | 12 +- t/multisub/pos_validated_list.t | 32 +++++ t/multisub/validated_hash.t | 33 +++++ 21 files changed, 520 insertions(+), 497 deletions(-) delete mode 100644 lib/Rex/Controller.pm delete mode 100644 lib/Rex/Controller/Base.pm delete mode 100644 lib/Rex/Controller/Function.pm delete mode 100644 lib/Rex/Controller/Resource.pm delete mode 100644 lib/Rex/Controller/Task.pm create mode 100644 lib/Rex/DSL/Common.pm create mode 100644 lib/Rex/MultiSub.pm create mode 100644 lib/Rex/MultiSub/Function.pm create mode 100644 lib/Rex/MultiSub/LookupTable.pm create mode 100644 lib/Rex/MultiSub/PosValidatedList.pm create mode 100644 lib/Rex/MultiSub/Resource.pm create mode 100644 lib/Rex/MultiSub/ValidatedHash.pm create mode 100644 t/multisub/pos_validated_list.t create mode 100644 t/multisub/validated_hash.t diff --git a/lib/Rex.pm b/lib/Rex.pm index 8c1dddb08..2ec2f4dc0 100644 --- a/lib/Rex.pm +++ b/lib/Rex.pm @@ -94,10 +94,13 @@ BEGIN { use Rex::Report; use Rex::Notify; use Rex::Require; - use Rex::Controller; - use Rex::Controller::Task; - use Rex::Controller::Function; - use Rex::Controller::Resource; + + use Rex::MultiSub; + use Rex::MultiSub::PosValidatedList; + use Rex::MultiSub::ValidatedHash; + use Rex::MultiSub::Resource; + use Rex::MultiSub::Function; + use File::Basename; eval { Net::SSH2->require; }; } @@ -132,25 +135,6 @@ has output => ( }, ); -sub push_run_stage { - my ( $self, $ctrl, $code ) = @_; - my $current = $self->run_stages; - push @{$current}, $ctrl; - - $self->_set_run_stages($current); - - $code->(); - - $current = $self->run_stages; - pop @{$current}; - $self->_set_run_stages($current); -} - -sub current_run_stage { - my ($self) = @_; - $self->run_stages->[-1]; -} - sub push_lib_to_inc { my $path = shift; diff --git a/lib/Rex/Controller.pm b/lib/Rex/Controller.pm deleted file mode 100644 index e70358596..000000000 --- a/lib/Rex/Controller.pm +++ /dev/null @@ -1,35 +0,0 @@ -# -# (c) Jan Gehring -# -# vim: set ts=2 sw=2 tw=0: -# vim: set expandtab: - -package Rex::Controller; - -use strict; -use warnings; - -# VERSION - -use Moose; - -has app => ( - is => 'ro', - isa => 'Rex', - lazy => 1, - default => sub { - Rex->instance; - }, -); - -has params => ( - is => 'ro', - isa => 'HashRef' -); - -sub param { - my ( $self, $what ) = @_; - return $self->params->{$what} if ($what); -} - -1; diff --git a/lib/Rex/Controller/Base.pm b/lib/Rex/Controller/Base.pm deleted file mode 100644 index 11acde952..000000000 --- a/lib/Rex/Controller/Base.pm +++ /dev/null @@ -1,36 +0,0 @@ -# -# (c) Jan Gehring -# -# vim: set ts=2 sw=2 tw=0: -# vim: set expandtab: - -package Rex::Controller::Base; - -use strict; -use warnings; - -# VERSION - -use Moose; - -extends qw(Rex::Controller); - -has params => ( - is => 'ro', - isa => 'HashRef', - writer => '_set_params', -); - -sub param { - my ( $self, $what ) = @_; - return $self->params->{$what} if ($what); -} - -sub set_param { - my ( $self, $what, $value ) = @_; - my $p = $self->params; - $p->{$what} = $value; - $self->_set_params($p); -} - -1; diff --git a/lib/Rex/Controller/Function.pm b/lib/Rex/Controller/Function.pm deleted file mode 100644 index 245c1a350..000000000 --- a/lib/Rex/Controller/Function.pm +++ /dev/null @@ -1,18 +0,0 @@ -# -# (c) Jan Gehring -# -# vim: set ts=2 sw=2 tw=0: -# vim: set expandtab: - -package Rex::Controller::Function; - -use strict; -use warnings; - -# VERSION - -use Moose; - -extends qw(Rex::Controller::Base); - -1; diff --git a/lib/Rex/Controller/Resource.pm b/lib/Rex/Controller/Resource.pm deleted file mode 100644 index 96d0aa633..000000000 --- a/lib/Rex/Controller/Resource.pm +++ /dev/null @@ -1,18 +0,0 @@ -# -# (c) Jan Gehring -# -# vim: set ts=2 sw=2 tw=0: -# vim: set expandtab: - -package Rex::Controller::Resource; - -use strict; -use warnings; - -# VERSION - -use Moose; - -extends qw(Rex::Controller::Base); - -1; diff --git a/lib/Rex/Controller/Task.pm b/lib/Rex/Controller/Task.pm deleted file mode 100644 index a271cc4d3..000000000 --- a/lib/Rex/Controller/Task.pm +++ /dev/null @@ -1,23 +0,0 @@ -# -# (c) Jan Gehring -# -# vim: set ts=2 sw=2 tw=0: -# vim: set expandtab: - -package Rex::Controller::Task; - -use strict; -use warnings; - -# VERSION - -use Moose; - -extends qw(Rex::Controller::Base); - -has task => ( - is => 'ro', - isa => 'Rex::Task', -); - -1; diff --git a/lib/Rex/DSL/Common.pm b/lib/Rex/DSL/Common.pm new file mode 100644 index 000000000..fae259201 --- /dev/null +++ b/lib/Rex/DSL/Common.pm @@ -0,0 +1,27 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::DSL::Common; + +use strict; +use warnings; + +# VERSION + +require Exporter; +require Rex::Config; +use Data::Dumper; +use base qw(Exporter); +use vars qw(@EXPORT); +use MooseX::Params::Validate; + +@EXPORT = qw(dsl); + +sub dsl { + my ( $name, $options, $function ) = @_; +} + +1; diff --git a/lib/Rex/Function/Common.pm b/lib/Rex/Function/Common.pm index 568bc36b8..b9cad940d 100644 --- a/lib/Rex/Function/Common.pm +++ b/lib/Rex/Function/Common.pm @@ -20,106 +20,9 @@ use MooseX::Params::Validate; @EXPORT = qw(function); -my $__lookup_table; - sub function { my ( $name, $options, $function ) = @_; my $name_save = $name; - - my $caller_pkg = caller; - - if ( ref $options eq "CODE" ) { - $function = $options; - $options = {}; - } - - $options->{name_idx} //= 0; - $options->{params_list} //= [ name => { isa => 'Str' }, ]; - - my $app = Rex->instance; - my $c = Rex::Controller::Function->new( app => $app ); - - push @{ $__lookup_table->{$name} }, - { - options => $options, - code => $function, - }; - - # TODO add dry run - # TODO add reporting - my $func = sub { - $app->output->print_s( - { title => $name, msg => $_[ $options->{name_idx} ] } ); - my $ret = {}; - eval { - my $found = 0; - for my $f ( - sort { - scalar( @{ $b->{options}->{params_list} } ) <=> - scalar( @{ $a->{options}->{params_list} } ) - } @{ $__lookup_table->{$name} } - ) - { - my @args; - eval { - my @_x = @{ $f->{options}->{params_list} }; - my @order = map { $_x[$_] } grep { $_ & 1 } 1 .. $#_x; - - @args = pos_validated_list( - \@_, @order, - MX_PARAMS_VALIDATE_NO_CACHE => 1, - MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1 - ); - - # get only the checked parameter inside @args array. - @args = splice( @args, 0, scalar(@order) ); - - $found = 1; - 1; - } or do { - - # TODO catch no "X parameter was given" errors - next; - }; - - my @rest_args = splice( @_, scalar(@args), scalar(@_) ); - if ( scalar(@rest_args) % 2 != 0 ) { - die "Wrong number of arguments for $name function."; - } - my %arg_options = @rest_args; - - # TODO check for common parameters like - # * timeout - # * only_notified - # * only_if - # * unless - # * creates - - $app->push_run_stage( - $c, - sub { - $ret = $f->{code}->( $c, @args, %arg_options ); - } - ); - last; - } - if ( !$found ) { - die "Function $name for provided parameter not found."; - } - $app->output->endln_ok(); - 1; - } or do { - $app->output->endln_failed(); - die "Error running command: $name.\nError: $@\n"; - }; - - if (wantarray) { - return split( /\r?\n/, $ret->{value} ); - } - - return $ret->{value}; - }; - if ( $name_save !~ m/^[a-zA-Z_][a-zA-Z0-9_]+$/ ) { Rex::Logger::info( "Please use only the following characters for function names:", "warn" ); @@ -129,41 +32,16 @@ sub function { die "Wrong function name syntax."; } - my ( $class, $file, @tmp ) = caller; - - if (!$class->can($name) - && $name_save =~ m/^[a-zA-Z_][a-zA-Z0-9_]+$/ ) - { - no strict 'refs'; - Rex::Logger::debug("Registering resource: ${class}::$name_save"); + my $sub = Rex::MultiSub::Function->new( + name => $name_save, + function => $function, + params_list => $options->{params_list}, + test_wantarray => 1, + ); - my $code = $_[-2]; - *{"${class}::$name_save"} = $func; - use strict; - } - elsif ( ( $class ne "main" && $class ne "Rex::CLI" ) - && !$class->can($name_save) - && $name_save =~ m/^[a-zA-Z_][a-zA-Z0-9_]+$/ ) - { - # if not in main namespace, register the function as a sub - no strict 'refs'; - Rex::Logger::debug( - "Registering function (not main namespace): ${class}::$name_save"); - my $code = $_[-2]; - *{"${class}::$name_save"} = $func; - - use strict; - } - - if ( exists $options->{export} && $options->{export} ) { - no strict 'refs'; + my ( $class, $file, @tmp ) = caller; - # register in caller namespace - push @{ $caller_pkg . "::ISA" }, "Rex::Exporter" - unless ( grep { $_ eq "Rex::Exporter" } @{ $caller_pkg . "::ISA" } ); - push @{ $caller_pkg . "::EXPORT" }, $name_save; - use strict; - } + $sub->export( $class, $options->{export} ); } 1; diff --git a/lib/Rex/Function/run.pm b/lib/Rex/Function/run.pm index 625f187f8..9b36511b4 100644 --- a/lib/Rex/Function/run.pm +++ b/lib/Rex/Function/run.pm @@ -14,6 +14,7 @@ use warnings; use Rex -minimal; use Rex::Function::Common; use Rex::Helper::Path; +use Data::Dumper; # define run() function # the run() function has 2 prototypes with different parameters @@ -65,9 +66,9 @@ function "run", { }; sub _run_command { - my $c = shift; my $cmd = shift; my ( $args, $options, $path ); + my $app = Rex->instance; if ( ref $_[0] eq "ARRAY" ) { $args = shift; @@ -103,13 +104,13 @@ sub _run_command { } if ( $? == 127 ) { - $c->app->output->stash( + $app->output->stash( error_info => "Command not found.", error_code => $? ); } elsif ( $? != 0 ) { - $c->app->output->stash( error_info => "Return code: $?", error_code => $? ); + $app->output->stash( error_info => "Return code: $?", error_code => $? ); } my $ret = {}; diff --git a/lib/Rex/Interface/Executor/Default.pm b/lib/Rex/Interface/Executor/Default.pm index d52193365..1abaaacea 100644 --- a/lib/Rex/Interface/Executor/Default.pm +++ b/lib/Rex/Interface/Executor/Default.pm @@ -35,32 +35,26 @@ sub exec { eval { my $code = $task->code; - my $c = Rex::Controller::Task->new( app => $self->app, params => $opts ); - $self->app->push_run_stage( - $c, - sub { - Rex::Hook::run_hook( task => "before_execute", $task->name, @_ ); - - if ($wantarray) { - if ( ref $opts eq "ARRAY" ) { - @ret = $code->( $self->app->current_run_stage ); - } - else { - @ret = $code->( $self->app->current_run_stage ); - } - } - else { - if ( ref $opts eq "ARRAY" ) { - $ret[0] = $code->( $self->app->current_run_stage ); - } - else { - $ret[0] = $code->( $self->app->current_run_stage ); - } - } - - Rex::Hook::run_hook( task => "after_execute", $task->name, @_ ); + Rex::Hook::run_hook( task => "before_execute", $task->name, @_ ); + + if ($wantarray) { + if ( ref $opts eq "ARRAY" ) { + @ret = $code->( @{$opts} ); + } + else { + @ret = $code->( $opts, $args ); + } + } + else { + if ( ref $opts eq "ARRAY" ) { + $ret[0] = $code->( @{$opts} ); + } + else { + $ret[0] = $code->( $opts, $args ); } - ); + } + + Rex::Hook::run_hook( task => "after_execute", $task->name, @_ ); }; my $error = $@; diff --git a/lib/Rex/MultiSub.pm b/lib/Rex/MultiSub.pm new file mode 100644 index 000000000..b713e66d3 --- /dev/null +++ b/lib/Rex/MultiSub.pm @@ -0,0 +1,115 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::MultiSub; + +use strict; +use warnings; + +# VERSION + +use Moose; +use MooseX::Params::Validate; +use Rex::MultiSub::LookupTable; + +use Data::Dumper; +use Carp; + +has methods => ( + is => 'rw', + isa => 'Rex::MultiSub::LookupTable', + default => sub { + Rex::MultiSub::LookupTable->instance; + } +); + +has name => ( is => 'ro', isa => 'Str' ); +has function => ( is => 'ro', isa => 'CodeRef' ); +has params_list => ( is => 'ro', isa => 'ArrayRef' ); + +sub validate { } +sub error { } + +sub BUILD { + my ($self) = @_; + $self->methods->add( $self->name, $self->params_list, $self->function ); +} + +sub export { + my ( $self, $ns, $global ) = @_; + + my $name = $self->name; + + no strict 'refs'; + no warnings 'redefine'; + + *{"${ns}::$name"} = sub { + $self->dispatch(@_); + }; + + if ($global) { + + # register in caller namespace + push @{ $ns . "::ISA" }, "Rex::Exporter" + unless ( grep { $_ eq "Rex::Exporter" } @{ $ns . "::ISA" } ); + push @{ $ns . "::EXPORT" }, $self->name; + } + + use strict; + use warnings; +} + +sub dispatch { + my ( $self, @args ) = @_; + + my @errors; + my $exec; + my @all_args; + my $found = 0; + + for my $f ( + sort { + scalar( @{ $b->{params_list} } ) <=> + scalar( @{ $a->{params_list} } ) + } @{ $self->methods->data->{ $self->name } } + ) + { + + eval { + @all_args = $self->validate( $f, @args ); + $found = 1; + 1; + } or do { + push @errors, $@; + + # print "Err: $@\n"; + # TODO catch no "X parameter was given" errors + next; + }; + + $exec = $f->{code}; + + last; + } + if ( !$found ) { + my @err_msg; + for my $err (@errors) { + my ($fline) = split( /\n/, $err ); + push @err_msg, $fline; + } + + $self->error(@err_msg); + } + + $self->call( $exec, @all_args ); +} + +sub call { + my ( $self, $code, @args ) = @_; + $code->(@args); +} + +1; diff --git a/lib/Rex/MultiSub/Function.pm b/lib/Rex/MultiSub/Function.pm new file mode 100644 index 000000000..2f4fadd69 --- /dev/null +++ b/lib/Rex/MultiSub/Function.pm @@ -0,0 +1,54 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::MultiSub::Function; + +use strict; +use warnings; + +# VERSION + +use Moose; +use MooseX::Params::Validate; +use Rex::MultiSub::LookupTable; + +use Data::Dumper; +use Carp; + +extends qw(Rex::MultiSub::PosValidatedList); + +has test_wantarray => ( + is => 'ro', + isa => 'Bool', + default => sub { 0 }, +); + +override call => sub { + my ( $self, $code, @args ) = @_; + + # TODO check for common parameters like + # * timeout + # * only_notified + # * only_if + # * unless + # * creates + # * on_change + # * ensure + + # TODO migrate reporting and error handling from Rex::Resource + # TODO remove Rex::Resource class + + my $ret = $code->(@args); + + if ( $self->test_wantarray && wantarray ) { + return split( /\n/, $ret->{value} ); + } + else { + return $ret->{value}; + } +}; + +1; diff --git a/lib/Rex/MultiSub/LookupTable.pm b/lib/Rex/MultiSub/LookupTable.pm new file mode 100644 index 000000000..37498d17c --- /dev/null +++ b/lib/Rex/MultiSub/LookupTable.pm @@ -0,0 +1,31 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::MultiSub::LookupTable; + +use strict; +use warnings; + +# VERSION + +use MooseX::Singleton; + +has data => ( + is => 'ro', + isa => 'HashRef', + default => sub { {} }, +); + +sub add { + my ( $self, $name, $params_list, $code ) = @_; + push @{ $self->data->{$name} }, + { + params_list => $params_list, + code => $code, + }; +} + +1; diff --git a/lib/Rex/MultiSub/PosValidatedList.pm b/lib/Rex/MultiSub/PosValidatedList.pm new file mode 100644 index 000000000..ed4dc6d29 --- /dev/null +++ b/lib/Rex/MultiSub/PosValidatedList.pm @@ -0,0 +1,47 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::MultiSub::PosValidatedList; + +use strict; +use warnings; + +# VERSION + +use Moose; +use MooseX::Params::Validate; +use Rex::MultiSub::LookupTable; + +use Data::Dumper; +use Carp; + +extends qw(Rex::MultiSub); + +override validate => sub { + my ( $self, $func_opts, @args ) = @_; + + my @_x = @{ $func_opts->{params_list} }; + my @order = map { $_x[$_] } grep { $_ & 1 } 1 .. $#_x; + + my @v_args = pos_validated_list( + \@args, @order, + MX_PARAMS_VALIDATE_NO_CACHE => 1, + MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1 + ); + + return @v_args; +}; + +override error => sub { + my ( $self, @err_msg ) = @_; + + my $name = $self->name; + croak "Function $name for provided parameter not found.\nErrors:\n" + . join( "\n", @err_msg ); +}; + +1; + diff --git a/lib/Rex/MultiSub/Resource.pm b/lib/Rex/MultiSub/Resource.pm new file mode 100644 index 000000000..e3ba665e9 --- /dev/null +++ b/lib/Rex/MultiSub/Resource.pm @@ -0,0 +1,63 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::MultiSub::Resource; + +use strict; +use warnings; + +# VERSION + +use Moose; +use MooseX::Params::Validate; +use Rex::MultiSub::LookupTable; + +use Data::Dumper; +use Carp; + +extends qw(Rex::MultiSub::ValidatedHash); + +override validate => sub { + my ( $self, $func_opts, @args ) = @_; + my @modified_args = @args; + my $name = shift @modified_args; + + # some defaults maybe a coderef, so we need to execute this now + my @_x = @{ $func_opts->{params_list} }; + my %_x = @_x; + for my $k ( keys %_x ) { + if ( ref $_x{$k}->{default} eq "CODE" ) { + $_x{$k}->{default} = $_x{$k}->{default}->(@args); + } + } + my %args = validated_hash( + \@modified_args, %_x, + MX_PARAMS_VALIDATE_NO_CACHE => 1, + MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1 + ); + + return ( $args[0], %args ); +}; + +override call => sub { + my ( $self, $code, @args ) = @_; + + # TODO check for common parameters like + # * timeout + # * only_notified + # * only_if + # * unless + # * creates + # * on_change + # * ensure + + # TODO migrate reporting and error handling from Rex::Resource + # TODO remove Rex::Resource class + + $code->(@args); +}; + +1; diff --git a/lib/Rex/MultiSub/ValidatedHash.pm b/lib/Rex/MultiSub/ValidatedHash.pm new file mode 100644 index 000000000..10f8bf2e0 --- /dev/null +++ b/lib/Rex/MultiSub/ValidatedHash.pm @@ -0,0 +1,52 @@ +# +# (c) Jan Gehring +# +# vim: set ts=2 sw=2 tw=0: +# vim: set expandtab: + +package Rex::MultiSub::ValidatedHash; + +use strict; +use warnings; + +# VERSION + +use Moose; +use MooseX::Params::Validate; +use Rex::MultiSub::LookupTable; + +use Data::Dumper; +use Carp; + +extends qw(Rex::MultiSub); + +override validate => sub { + my ( $self, $func_opts, @args ) = @_; + + # some defaults maybe a coderef, so we need to execute this now + my @_x = @{ $func_opts->{params_list} }; + my %_x = @_x; + for my $k ( keys %_x ) { + if ( ref $_x{$k}->{default} eq "CODE" ) { + $_x{$k}->{default} = $_x{$k}->{default}->(@args); + } + } + + my %args = validated_hash( + \@args, %_x, + MX_PARAMS_VALIDATE_NO_CACHE => 1, + MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1 + ); + + return %args; +}; + +override error => sub { + my ( $self, @err_msg ) = @_; + + my $name = $self->name; + croak "Function $name for provided parameter not found.\nErrors:\n" + . join( "\n", @err_msg ); +}; + +1; diff --git a/lib/Rex/Resource/Common.pm b/lib/Rex/Resource/Common.pm index 0e658904b..b3cf0cc9e 100644 --- a/lib/Rex/Resource/Common.pm +++ b/lib/Rex/Resource/Common.pm @@ -18,6 +18,7 @@ use Rex::Resource; use Data::Dumper; use MooseX::Params::Validate; use Hash::Merge qw/merge/; +use Rex::MultiSub::Resource; use base qw(Exporter); use vars qw(@EXPORT); @@ -85,192 +86,15 @@ sub resource { die "Wrong resource name syntax."; } - push @{ $__lookup_table->{$name} }, - { - options => $options, - code => $function, - }; + my $sub = Rex::MultiSub::Resource->new( + name => $name_save, + function => $function, + params_list => $options->{params_list}, + ); my ( $class, $file, @tmp ) = caller; - # this function is responsible to lookup the right resource code - # every resource can have multiple resource code depending on its parameters. - my $call_func = sub { - my $app = Rex->instance; - - $app->output->print_s( { title => $name, msg => $_[0] } ); - - my @errors; - eval { - my $found = 0; - for my $f ( - sort { - scalar( @{ $b->{options}->{params_list} } ) <=> - scalar( @{ $a->{options}->{params_list} } ) - } @{ $__lookup_table->{$name} } - ) - { - my %args; - eval { - my @modified_args = @_; - my $name = shift @modified_args; - - # some defaults maybe a coderef, so we need to execute this now - my @_x = @{ $f->{options}->{params_list} }; - my %_x = @_x; - for my $k ( keys %_x ) { - if ( ref $_x{$k}->{default} eq "CODE" ) { - $_x{$k}->{default} = $_x{$k}->{default}->(@_); - } - } - %args = validated_hash( - \@modified_args, %_x, - MX_PARAMS_VALIDATE_NO_CACHE => 1, - MX_PARAMS_VALIDATE_ALLOW_EXTRA => 1 - ); - - $found = 1; - 1; - } or do { - push @errors, $@; - - # print "Err: $@\n"; - # TODO catch no "X parameter was given" errors - next; - }; - - # TODO check for common parameters like - # * timeout - # * only_notified - # * only_if - # * unless - # * creates - # * on_change - # * ensure - - my $res = Rex::Resource->new( - type => "${class}::$name", - name => $name, - display_name => ( - $options->{name} - || ( $options->{export} ? $name : "${caller_pkg}::${name}" ) - ), - cb => $f->{code}, - ); - - my $c = Rex::Controller::Resource->new( app => $app, params => \%args ); - my @args = @_; - $app->push_run_stage( - $c, - sub { - $res->call( $c, @args ); - } - ); - last; - } - if ( !$found ) { - my @err_msg; - for my $err (@errors) { - my ($fline) = split( /\n/, $err ); - push @err_msg, $fline; - } - croak "Resource $name for provided parameter not found.\nErrors:\n" - . join( "\n", @err_msg ); - } - 1; - } or do { - $app->output->endln_failed(); - die "Error executing resource: $name.\nError: $@\n"; - }; - - $app->output->endln_ok(); - }; - - # this is the code that gets registered into the namespace - my $func = sub { - - # test if first parameter to resource is a hash - # if so, we have multiple instances of this resource and we need to call - # every of them. - # - # Example: - # kmod { - # foo => { ensure => "present", }, - # bar => { ensure => "present", }, - # }; - if ( ref $_[0] eq "HASH" ) { - my ($res_hash) = @_; - - for my $n ( keys %{$res_hash} ) { - my $this_p = $res_hash->{$n}; - - # test if the second parameter is also a hash, this means we need to - # merge this hash as default values into the parameters of the - # first hash. - if ( $_[1] && ref $_[1] eq "HASH" ) { - $this_p = merge( $this_p, $_[1] ); - } - - # now we call the resource code for each one of the first hash. - $call_func->( $n, %{$this_p} ); - } - - # nothing to do anymore, so we can just return. - # a resource doesn't have a specified return value. - return undef; - } - - # test if the first parameter to resource is an array - # if so, we have to iterate over the array and call the resource code for - # each of them. - if ( ref $_[0] eq "ARRAY" ) { - my $all_res = shift; - for my $n ( @{$all_res} ) { - $call_func->( $n, @_ ); - } - - # nothing to do anymore, so we can just return. - # a resource doesn't have a specified return value. - return undef; - } - - # default resource call - $call_func->(@_); - }; - - if (!$class->can($name) - && $name_save =~ m/^[a-zA-Z_][a-zA-Z0-9_]+$/ ) - { - no strict 'refs'; - Rex::Logger::debug("Registering resource: ${class}::$name_save"); - - my $code = $_[-2]; - *{"${class}::$name_save"} = $func; - use strict; - } - elsif ( ( $class ne "main" && $class ne "Rex::CLI" ) - && !$class->can($name_save) - && $name_save =~ m/^[a-zA-Z_][a-zA-Z0-9_]+$/ ) - { - # if not in main namespace, register the task as a sub - no strict 'refs'; - Rex::Logger::debug( - "Registering resource (not main namespace): ${class}::$name_save"); - my $code = $_[-2]; - *{"${class}::$name_save"} = $func; - - use strict; - } - - if ( exists $options->{export} && $options->{export} ) { - no strict 'refs'; - - # register in caller namespace - push @{ $caller_pkg . "::ISA" }, "Rex::Exporter" - unless ( grep { $_ eq "Rex::Exporter" } @{ $caller_pkg . "::ISA" } ); - push @{ $caller_pkg . "::EXPORT" }, $name_save; - use strict; - } + $sub->export( $class, $options->{export} ); } sub resource_name { diff --git a/lib/Rex/Resource/Provider.pm b/lib/Rex/Resource/Provider.pm index 0eec10c5c..f8ecbd474 100644 --- a/lib/Rex/Resource/Provider.pm +++ b/lib/Rex/Resource/Provider.pm @@ -12,6 +12,7 @@ use warnings; # VERSION use Moose; +use Data::Dumper; has __version__ => ( is => 'ro', @@ -72,4 +73,15 @@ has message => ( default => sub { '' }, ); +sub DEMOLISH { + my ($self) = @_; + $self->report; +} + +sub report { + my ($self) = @_; + + # TODO reporting +} + 1; diff --git a/lib/Rex/Resource/firewall.pm b/lib/Rex/Resource/firewall.pm index 58bc2cd38..5cbe709cc 100644 --- a/lib/Rex/Resource/firewall.pm +++ b/lib/Rex/Resource/firewall.pm @@ -114,12 +114,18 @@ resource "firewall", { ], }, sub { - my ($c) = @_; + my ( $name, %args ) = @_; my $provider = - $c->param("provider") || case ( lc(operating_system), $__provider ); + $args{provider} || case ( lc(operating_system), $__provider ); - return ( $provider, $c->params ); + # TODO define provider type automatically. + $provider->require; + my $provider_o = + $provider->new( type => "firewall", name => $name, config => \%args ); + $provider_o->process; + + # return ( $provider, $c->params ); # if ( my $logging = $rule_config->{logging} ) { # if ( $provider_o->logging($logging) ) { diff --git a/t/multisub/pos_validated_list.t b/t/multisub/pos_validated_list.t new file mode 100644 index 000000000..fec433355 --- /dev/null +++ b/t/multisub/pos_validated_list.t @@ -0,0 +1,32 @@ +use Test::More tests => 2; + +use Rex::MultiSub::PosValidatedList; + +my $sub = Rex::MultiSub::PosValidatedList->new( + name => "testfunc", + function => sub { + my ($name) = @_; + return $name; + }, + params_list => [ + name => { isa => 'Str' }, + ], +); +$sub->export("main"); + +my $sub2 = Rex::MultiSub::PosValidatedList->new( + name => "testfunc", + function => sub { + my ( $name, $age ) = @_; + return $name . "=" . $age; + }, + params_list => [ + name => { isa => 'Str' }, + age => { isa => 'Int' }, + ], +); +$sub2->export("main"); + +is( testfunc("rex"), "rex", "called function with name key" ); +is( testfunc( "rex", 5 ), "rex=5", "called function with name and age key" ); + diff --git a/t/multisub/validated_hash.t b/t/multisub/validated_hash.t new file mode 100644 index 000000000..bf2c810d9 --- /dev/null +++ b/t/multisub/validated_hash.t @@ -0,0 +1,33 @@ +use Test::More tests => 2; + +use Rex::MultiSub::ValidatedHash; + +my $sub = Rex::MultiSub::ValidatedHash->new( + name => "testfunc", + function => sub { + my (%x) = @_; + return $x{name}; + }, + params_list => [ + name => { isa => 'Str' }, + ], +); +$sub->export("main"); + +my $sub2 = Rex::MultiSub::ValidatedHash->new( + name => "testfunc", + function => sub { + my (%x) = @_; + return $x{name} . "=" . $x{age}; + }, + params_list => [ + name => { isa => 'Str' }, + age => { isa => 'Int' }, + ], +); +$sub2->export("main"); + +is( testfunc( name => "rex" ), "rex", "called function with name key" ); +is( testfunc( name => "rex", age => 5 ), + "rex=5", "called function with name and age key" ); + From e6e428f4afc1af6a4e9baaa4d476b6b9a4806173 Mon Sep 17 00:00:00 2001 From: krimdomu Date: Sat, 13 Aug 2016 19:51:42 +0200 Subject: [PATCH 6/7] allow resource call with hash and arrayref --- lib/Rex/MultiSub/Resource.pm | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/lib/Rex/MultiSub/Resource.pm b/lib/Rex/MultiSub/Resource.pm index e3ba665e9..b334e6586 100644 --- a/lib/Rex/MultiSub/Resource.pm +++ b/lib/Rex/MultiSub/Resource.pm @@ -52,12 +52,26 @@ override call => sub { # * unless # * creates # * on_change + # * on_before_change # * ensure # TODO migrate reporting and error handling from Rex::Resource # TODO remove Rex::Resource class + # TODO add default values for $args[1] if $args[0] is hash - $code->(@args); + if ( ref $args[0] eq "HASH" ) { + for my $k_name ( keys %{ $args[0] } ) { + $code->( $k_name, %{ $args[0]->{$k_name} } ); + } + } + elsif ( ref $args[0] eq "ARRAY" ) { + for my $v_name ( @{ $args[0] } ) { + $code->( $v_name, @args[ 1 .. $#args ] ); + } + } + else { + $code->(@args); + } }; 1; From d303fcbef47e3f46bd39e2a79d2951ce06193511 Mon Sep 17 00:00:00 2001 From: krimdomu Date: Sat, 13 Aug 2016 19:52:00 +0200 Subject: [PATCH 7/7] removed controller class --- lib/Rex/Function/Fs/is_file.pm | 2 +- lib/Rex/Function/run.pm | 78 ++++++++++++++++++- lib/Rex/MultiSub/Function.pm | 2 + lib/Rex/Resource/file.pm | 24 +++--- lib/Rex/Resource/kernel.pm | 14 ++-- lib/Rex/Resource/kernel/Provider/base.pm | 6 +- .../Resource/kernel/Provider/linux/systemd.pm | 4 +- 7 files changed, 107 insertions(+), 23 deletions(-) diff --git a/lib/Rex/Function/Fs/is_file.pm b/lib/Rex/Function/Fs/is_file.pm index 99a3b067e..0b0d2050b 100644 --- a/lib/Rex/Function/Fs/is_file.pm +++ b/lib/Rex/Function/Fs/is_file.pm @@ -36,7 +36,7 @@ function "is_file", { # second, third, ... parameter are the real parameter the user passed to the # function call. sub { - my ( $c, $file ) = @_; + my ($file) = @_; $file = resolv_path($file); my $fs = Rex::Interface::Fs->create; diff --git a/lib/Rex/Function/run.pm b/lib/Rex/Function/run.pm index 9b36511b4..aa741a721 100644 --- a/lib/Rex/Function/run.pm +++ b/lib/Rex/Function/run.pm @@ -80,13 +80,86 @@ sub _run_command { $options = {@_} if @_; my $exec = Rex::Interface::Exec->create; + my $fs = Rex::Interface::Fs->create; if ( !Rex::Config->get_no_path_cleanup() ) { $path = join( ":", Rex::Config->get_path() ); } + my $quoter = Net::OpenSSH::ShellQuoter->quoter( $exec->shell->name ); + + # test for only_if condition + # if only_if is set, the command should only run when the command is + # successful + if ( $options->{only_if} ) { + my $only_if = $options->{only_if}; + my $only_if_cmd; + + if ( ref $only_if eq "ARRAY" ) { + $only_if_cmd = shift @{$only_if}; + $only_if_cmd = + "$only_if_cmd " . join( " ", map { $quoter->quote($_) } @{$only_if} ); + } + else { + $only_if_cmd = $only_if; + } + + $exec->exec( $only_if_cmd, $path ); + if ( $? != 0 ) { + $app->output->stash( + error_info => "only_if condition not met", + exit_code => $?, + ); + return { exit_code => 0, }; + } + } + + # test for unless condition + # if unless is set, the command should only run when the command is + # not successful + if ( $options->{unless} ) { + my $unless = $options->{unless}; + my $unless_cmd; + + if ( ref $unless eq "ARRAY" ) { + $unless_cmd = shift @{$unless}; + $unless_cmd = + "$unless_cmd " . join( " ", map { $quoter->quote($_) } @{$unless} ); + } + else { + $unless_cmd = $unless; + } + + $exec->exec( $unless_cmd, $path ); + if ( $? == 0 ) { + $app->output->stash( + error_info => "unless condition not met", + exit_code => $?, + ); + return { exit_code => 0, }; + } + } + + # test for creates condition + # if creates is set, the command should only run when the file is not present + if ( $options->{creates} ) { + my $creates = + ref $options->{creates} eq "ARRAY" + ? $options->{creates} + : [ $options->{creates} ]; + + for my $c ( @{$creates} ) { + if ( $fs->is_file($c) || $fs->is_dir($c) ) { + $app->output->stash( + error_info => "creates condition met", + exit_code => 0, + ); + return { exit_code => 0, }; + } + } + } + if ( scalar @{$args} ) { - my $quoter = Net::OpenSSH::ShellQuoter->quoter( $exec->shell->name ); $cmd = "$cmd " . join( " ", map { $quoter->quote($_) } @{$args} ); } @@ -114,7 +187,8 @@ sub _run_command { } my $ret = {}; - $ret->{value} = $out; + $ret->{value} = $out; + $ret->{exit_code} = $?; return $ret; } diff --git a/lib/Rex/MultiSub/Function.pm b/lib/Rex/MultiSub/Function.pm index 2f4fadd69..370608fe6 100644 --- a/lib/Rex/MultiSub/Function.pm +++ b/lib/Rex/MultiSub/Function.pm @@ -43,6 +43,8 @@ override call => sub { my $ret = $code->(@args); + return unless $ret->{value}; + if ( $self->test_wantarray && wantarray ) { return split( /\n/, $ret->{value} ); } diff --git a/lib/Rex/Resource/file.pm b/lib/Rex/Resource/file.pm index cd943389a..9aab5c372 100644 --- a/lib/Rex/Resource/file.pm +++ b/lib/Rex/Resource/file.pm @@ -156,27 +156,31 @@ resource "file", { ], }, sub { - my ($c) = @_; - my $file_name = resolv_path( $c->param("name") ); + my ( $file_name, %args ) = @_; - my $provider = $c->param("provider") + my $provider = + $args{provider} || get_resource_provider( kernelname(), operating_system() ); + # TODO define provider type automatically. + $provider->require; + Rex::Logger::debug("Get file provider: $provider"); - if ( defined $c->param("source") ) { - $c->set_param( "source", - get_file_path( resolv_path( $c->param("source") ), caller() ) ); + if ( defined $args{source} ) { + $args{source} = get_file_path( resolv_path( $args{source} ), caller() ); if ( Rex::Config->get_environment - && -f $c->param("source") . "." . Rex::Config->get_environment ) + && -f $args{source} . "." . Rex::Config->get_environment ) { - $c->set_param( "source", - $c->param("source") . "." . Rex::Config->get_environment ); + $args{source} = $args{source} . "." . Rex::Config->get_environment; } } - return ( $provider, $c->params ); + my $provider_o = + $provider->new( type => "file", name => $file_name, config => \%args ); + $provider_o->process; + }; 1; diff --git a/lib/Rex/Resource/kernel.pm b/lib/Rex/Resource/kernel.pm index 3b8764894..1797f0c2c 100644 --- a/lib/Rex/Resource/kernel.pm +++ b/lib/Rex/Resource/kernel.pm @@ -65,20 +65,22 @@ resource "kmod", { ], }, sub { - my ($c) = @_; + my ( $name, %args ) = @_; # here we define the provider the resource should use. If someone want to use # a custom provider we will use this. Otherwise we try to detect the provider # automatically. - my $provider = $c->param("provider") + my $provider = $args{provider} || get_resource_provider( kernelname(), operating_system() ); + # TODO define provider type automatically. + $provider->require; + Rex::Logger::debug("Get kernel provider: $provider"); - # at the end we return the wanted provider and an hash reference containing - # all the parameters for this resource. - # here we just pass the parameters back without modifying them. - return ( $provider, $c->params ); + my $provider_o = + $provider->new( type => "kmod", name => $name, config => \%args ); + $provider_o->process; }; 1; diff --git a/lib/Rex/Resource/kernel/Provider/base.pm b/lib/Rex/Resource/kernel/Provider/base.pm index 1c350d467..dee866f84 100644 --- a/lib/Rex/Resource/kernel/Provider/base.pm +++ b/lib/Rex/Resource/kernel/Provider/base.pm @@ -12,6 +12,7 @@ use warnings; # VERSION use Moose; +use Data::Dumper; use Rex::Helper::Run; @@ -31,12 +32,11 @@ sub test { { return 1; } - elsif ( $self->config->{ensure} eq "absent" && !$count ) { + elsif ( $self->config->{ensure} eq "absent" && $count ) { return 1; } elsif ( $self->config->{ensure} eq "disabled" - && !$self->_is_enabled - && !$count ) + && ( $self->_is_enabled || $count ) ) { return 1; } diff --git a/lib/Rex/Resource/kernel/Provider/linux/systemd.pm b/lib/Rex/Resource/kernel/Provider/linux/systemd.pm index 9dca79e41..db1b47f9e 100644 --- a/lib/Rex/Resource/kernel/Provider/linux/systemd.pm +++ b/lib/Rex/Resource/kernel/Provider/linux/systemd.pm @@ -73,6 +73,7 @@ sub enabled { my $changed = $self->present; my $os_ver = operating_system_release(); + # TODO migrate to something like Rex::Interface::Fs->file() Rex::Commands::File::file( "/etc/modules-load.d/$mod_name.conf", ensure => "present", @@ -111,8 +112,9 @@ sub _is_enabled { my ($self) = @_; my $mod_name = $self->name; + my $fs = Rex::Interface::Fs->create; - return Rex::Commands::Fs::is_file("/etc/modules-load.d/$mod_name.conf"); + return $fs->is_file("/etc/modules-load.d/$mod_name.conf"); } 1;