1#
2# (c) Jan Gehring <jan.gehring@gmail.com>
3#
4# vim: set ts=2 sw=2 tw=0:
5# vim: set expandtab:
6
7=head1 NAME
8
9Rex::Task - The Task Object
10
11=head1 DESCRIPTION
12
13The Task Object. Typically you only need this class if you want to manipulate tasks after their initial creation.
14
15=head1 SYNOPSIS
16
17 use Rex::Task;
18
19 # create a new task
20 my $task = Rex::Task->new( name => 'testtask' );
21 $task->set_server('remoteserver');
22 $task->set_code( sub { say 'Hello'; } );
23 $task->modify( 'no_ssh', 1 );
24
25 # retrieve an existing task
26 use Rex::TaskList;
27
28 my $existing_task = Rex::TaskList->create->get_task('my_task');
29
30=head1 METHODS
31
32=cut
33
34package Rex::Task;
35
36use 5.010001;
37use strict;
38use warnings;
39use Data::Dumper;
40use Time::HiRes qw(time);
41
42our $VERSION = '1.13.4'; # VERSION
43
44use Rex::Logger;
45use Rex::TaskList;
46use Rex::Interface::Connection;
47use Rex::Interface::Executor;
48use Rex::Group::Entry::Server;
49use Rex::Profiler;
50use Rex::Hardware;
51use Rex::Interface::Cache;
52use Rex::Report;
53use Rex::Helper::Run;
54use Rex::Helper::Path;
55use Rex::Notify;
56use Carp;
57
58require Rex::Commands;
59
60require Rex::Args;
61
62=head2 new
63
64This is the constructor.
65
66 $task = Rex::Task->new(
67   func => sub { some_code_here },
68   server => [ @server ],
69   desc => $description,
70   no_ssh => $no_ssh,
71   hidden => $hidden,
72   auth => {
73     user      => $user,
74     password   => $password,
75     private_key => $private_key,
76     public_key  => $public_key,
77   },
78   before => [sub {}, sub {}, ...],
79   after  => [sub {}, sub {}, ...],
80   around => [sub {}, sub {}, ...],
81   before_task_start => [sub {}, sub {}, ...],
82   after_task_finished => [sub {}, sub {}, ...],
83   name => $task_name,
84   executor => Rex::Interface::Executor->create,
85   opts => {key1 => val1, key2 => val2, ...},
86   args => [arg1, arg2, ...],
87 );
88
89=cut
90
91sub new {
92  my $that  = shift;
93  my $proto = ref($that) || $that;
94  my $self  = {@_};
95
96  bless( $self, $proto );
97
98  if ( !exists $self->{name} ) {
99    die("You have to define a task name.");
100  }
101
102  $self->{no_ssh}   ||= 0;
103  $self->{func}     ||= sub { };
104  $self->{executor} ||= Rex::Interface::Executor->create;
105  $self->{opts}     ||= {};
106  $self->{args}     ||= [];
107
108  $self->{connection} = undef;
109
110  # set to true as default
111  if ( !exists $self->{exit_on_connect_fail} ) {
112    $self->{exit_on_connect_fail} = 1;
113  }
114
115  return $self;
116}
117
118=head2 connection
119
120Returns the current connection object.
121
122=cut
123
124sub connection {
125  my ($self) = @_;
126  if ( !exists $self->{connection} || !$self->{connection} ) {
127    $self->{connection} =
128      Rex::Interface::Connection->create( $self->get_connection_type );
129  }
130
131  $self->{connection};
132}
133
134sub set_connection {
135  my ( $self, $conn ) = @_;
136  $self->{connection} = $conn;
137}
138
139=head2 executor
140
141Returns the current executor object.
142
143=cut
144
145sub executor {
146  my ($self) = @_;
147  $self->{executor}->set_task($self);
148  return $self->{executor};
149}
150
151=head2 hidden
152
153Returns true if the task is hidden. (Should not be displayed on ,,rex -T''.)
154
155=cut
156
157sub hidden {
158  my ($self) = @_;
159  return $self->{hidden};
160}
161
162=head2 server
163
164Returns the servers on which the task should be executed as an ArrayRef.
165
166=cut
167
168sub server {
169  my ($self) = @_;
170
171  my @server = @{ $self->{server} };
172  my @ret    = ();
173
174  if ( ref( $server[-1] ) eq "HASH" ) {
175    Rex::deprecated(
176      undef, "0.40",
177      "Defining extra credentials within the task creation is deprecated.",
178      "Please use set auth => task => 'taskname' instead."
179    );
180
181    # use extra defined credentials
182    my $data = pop(@server);
183    $self->set_auth( "user",     $data->{'user'} );
184    $self->set_auth( "password", $data->{'password'} );
185
186    if ( exists $data->{"private_key"} ) {
187      $self->set_auth( "private_key", $data->{"private_key"} );
188      $self->set_auth( "public_key",  $data->{"public_key"} );
189    }
190  }
191
192  if ( ref( $self->{server} ) eq "ARRAY"
193    && scalar( @{ $self->{server} } ) > 0 )
194  {
195    for my $srv ( @{ $self->{server} } ) {
196      if ( ref($srv) eq "CODE" ) {
197        push( @ret, &$srv() );
198      }
199      else {
200        if ( ref $srv && $srv->isa("Rex::Group::Entry::Server") ) {
201          push( @ret, $srv->get_servers );
202        }
203        else {
204          push( @ret, $srv );
205        }
206      }
207    }
208  }
209  elsif ( ref( $self->{server} ) eq "CODE" ) {
210    push( @ret, &{ $self->{server} }() );
211  }
212  else {
213    push( @ret, Rex::Group::Entry::Server->new( name => "<local>" ) );
214  }
215
216  return [@ret];
217}
218
219=head2 set_server(@server)
220
221With this method you can set new servers on which the task should be executed on.
222
223=cut
224
225sub set_server {
226  my ( $self, @server ) = @_;
227  $self->{server} = \@server;
228}
229
230=head2 delete_server
231
232Delete every server registered to the task.
233
234=cut
235
236sub delete_server {
237  my ($self) = @_;
238  delete $self->{current_server};
239  delete $self->{server};
240  $self->rethink_connection;
241}
242
243=head2 current_server
244
245Returns the current server on which the tasks gets executed right now.
246
247=cut
248
249sub current_server {
250  my ($self) = @_;
251  return $self->{current_server}
252    || Rex::Group::Entry::Server->new( name => "<local>" );
253}
254
255=head2 desc
256
257Returns the description of a task.
258
259=cut
260
261sub desc {
262  my ($self) = @_;
263  return $self->{desc};
264}
265
266=head2 set_desc($description)
267
268Set the description of a task.
269
270=cut
271
272sub set_desc {
273  my ( $self, $desc ) = @_;
274  $self->{desc} = $desc;
275}
276
277=head2 is_remote
278
279Returns true (1) if the task will be executed remotely.
280
281=cut
282
283sub is_remote {
284  my ($self) = @_;
285  if ( exists $self->{current_server} ) {
286    if ( $self->{current_server} ne '<local>' ) {
287      return 1;
288    }
289  }
290  else {
291    if ( exists $self->{server} && scalar( @{ $self->{server} } ) > 0 ) {
292      return 1;
293    }
294  }
295
296  return 0;
297}
298
299=head2 is_local
300
301Returns true (1) if the task gets executed on the local host.
302
303=cut
304
305sub is_local {
306  my ($self) = @_;
307  return $self->is_remote() == 0 ? 1 : 0;
308}
309
310=head2 is_http
311
312Returns true (1) if the task gets executed over http protocol.
313
314=cut
315
316sub is_http {
317  my ($self) = @_;
318  return ( $self->{"connection_type"}
319      && lc( $self->{"connection_type"} ) eq "http" );
320}
321
322=head2 is_https
323
324Returns true (1) if the task gets executed over https protocol.
325
326=cut
327
328sub is_https {
329  my ($self) = @_;
330  return ( $self->{"connection_type"}
331      && lc( $self->{"connection_type"} ) eq "https" );
332}
333
334=head2 is_openssh
335
336Returns true (1) if the task gets executed with openssh.
337
338=cut
339
340sub is_openssh {
341  my ($self) = @_;
342  return ( $self->{"connection_type"}
343      && lc( $self->{"connection_type"} ) eq "openssh" );
344}
345
346=head2 want_connect
347
348Returns true (1) if the task will establish a connection to a remote system.
349
350=cut
351
352sub want_connect {
353  my ($self) = @_;
354  return $self->{no_ssh} == 0 ? 1 : 0;
355}
356
357=head2 get_connection_type
358
359This method tries to guess the right connection type for the task and returns it.
360
361Current return values are below:
362
363=over 4
364
365=item * SSH: connect to the remote server using Net::SSH2
366
367=item * OpenSSH: connect to the remote server using Net::OpenSSH
368
369=item * Local: runs locally (without any connections)
370
371=item * HTTP: uses experimental HTTP connection
372
373=item * HTTPS: uses experimental HTTPS connection
374
375=item * Fake: populate the connection properties, but do not connect
376
377So you can use this type to iterate over a list of remote hosts, but don't let rex build a connection. For example if you want to use Sys::Virt or other modules.
378
379=back
380
381=cut
382
383sub get_connection_type {
384  my ($self) = @_;
385
386  if ( $self->is_http ) {
387    return "HTTP";
388  }
389  elsif ( $self->is_https ) {
390    return "HTTPS";
391  }
392  elsif ( $self->is_remote && $self->is_openssh && $self->want_connect ) {
393    return "OpenSSH";
394  }
395  elsif ( $self->is_remote && $self->want_connect ) {
396    return Rex::Config->get_connection_type();
397  }
398  elsif ( $self->is_remote ) {
399    return "Fake";
400  }
401  else {
402    return "Local";
403  }
404}
405
406=head2 modify($key, $value)
407
408With this method you can modify values of the task.
409
410=cut
411
412sub modify {
413  my ( $self, $key, $value ) = @_;
414
415  if ( ref( $self->{$key} ) eq "ARRAY" ) {
416    push( @{ $self->{$key} }, $value );
417  }
418  else {
419    $self->{$key} = $value;
420  }
421
422  $self->rethink_connection;
423}
424
425=head2 rethink_connection
426
427Deletes current connection object.
428
429=cut
430
431sub rethink_connection {
432  my ($self) = @_;
433  delete $self->{connection};
434}
435
436=head2 user
437
438Returns the username the task will use.
439
440=cut
441
442sub user {
443  my ($self) = @_;
444  if ( exists $self->{auth} && $self->{auth}->{user} ) {
445    return $self->{auth}->{user};
446  }
447}
448
449=head2 set_user($user)
450
451Set the username of a task.
452
453=cut
454
455sub set_user {
456  my ( $self, $user ) = @_;
457  $self->{auth}->{user} = $user;
458}
459
460=head2 password
461
462Returns the password that will be used.
463
464=cut
465
466sub password {
467  my ($self) = @_;
468  if ( exists $self->{auth} && $self->{auth}->{password} ) {
469    return $self->{auth}->{password};
470  }
471}
472
473=head2 set_password($password)
474
475Set the password of the task.
476
477=cut
478
479sub set_password {
480  my ( $self, $password ) = @_;
481  $self->{auth}->{password} = $password;
482}
483
484=head2 name
485
486Returns the name of the task.
487
488=cut
489
490sub name {
491  my ($self) = @_;
492  return $self->{name};
493}
494
495=head2 code
496
497Returns the code of the task.
498
499=cut
500
501sub code {
502  my ($self) = @_;
503  return $self->{func};
504}
505
506=head2 set_code(\&code_ref)
507
508Set the code of the task.
509
510=cut
511
512sub set_code {
513  my ( $self, $code ) = @_;
514  $self->{func} = $code;
515}
516
517=head2 run_hook($server, $hook)
518
519This method is used internally to execute the specified hooks.
520
521=cut
522
523sub run_hook {
524  my ( $self, $server, $hook, @more_args ) = @_;
525  my $old_server;
526
527  for my $code ( @{ $self->{$hook} } ) {
528    if ( $hook eq "after" ) { # special case for after hooks
529      $code->(
530        $$server,
531        ( $self->{"__was_authenticated"} || 0 ),
532        { $self->get_opts }, @more_args
533      );
534    }
535    else {
536      $old_server = $$server if $server;
537      $code->( $$server, $server, { $self->get_opts }, @more_args );
538      if ( $old_server && $old_server ne $$server ) {
539        $self->{current_server} = $$server;
540      }
541    }
542  }
543}
544
545=head2 set_auth($key, $value)
546
547Set the authentication of the task.
548
549 $task->set_auth("user", "foo");
550 $task->set_auth("password", "bar");
551
552=cut
553
554sub set_auth {
555  my ( $self, $key, $value ) = @_;
556
557  if ( scalar(@_) > 3 ) {
558    my $_d = shift;
559    $self->{auth} = {@_};
560  }
561  else {
562    $self->{auth}->{$key} = $value;
563  }
564}
565
566=head2 merge_auth($server)
567
568Merges the authentication information from $server into the task.
569Tasks authentication information have precedence.
570
571=cut
572
573sub merge_auth {
574  my ( $self, $server ) = @_;
575
576  # merge auth hashs
577  # task auth as precedence
578  my %auth = $server->merge_auth( $self->{auth} );
579
580  return \%auth;
581}
582
583=head2 get_sudo_password
584
585Returns the sudo password.
586
587=cut
588
589sub get_sudo_password {
590  my ($self) = @_;
591
592  my $server = $self->connection->server;
593  my %auth   = $server->merge_auth( $self->{auth} );
594
595  return $auth{sudo_password};
596}
597
598=head2 parallelism
599
600Get the parallelism count of a task.
601
602=cut
603
604sub parallelism {
605  my ($self) = @_;
606  return $self->{parallelism};
607}
608
609=head2 set_parallelism($count)
610
611Set the parallelism of the task.
612
613=cut
614
615sub set_parallelism {
616  my ( $self, $para ) = @_;
617  $self->{parallelism} = $para;
618}
619
620=head2 connect($server)
621
622Initiate the connection to $server.
623
624=cut
625
626sub connect {
627  my ( $self, $server, %override ) = @_;
628
629  if ( !ref $server ) {
630    $server = Rex::Group::Entry::Server->new( name => $server );
631  }
632  $self->{current_server} = $server;
633
634  $self->run_hook( \$server, "before" );
635
636  # need to be called, in case of a run_task task call.
637  # see #788
638  $self->rethink_connection;
639
640  my $user = $self->user;
641
642  #print Dumper($self);
643  my $auth = $self->merge_auth($server);
644
645  if ( exists $override{auth} ) {
646    $auth = $override{auth};
647    $user = $auth->{user};
648  }
649
650  my $rex_int_conf = Rex::Commands::get("rex_internals");
651  Rex::Logger::debug( Dumper($rex_int_conf) );
652  Rex::Logger::debug("Auth-Information inside Task:");
653  for my $key ( keys %{$auth} ) {
654    my $data = $auth->{$key};
655    $data = Rex::Logger::masq( "%s", $data ) if $key eq 'password';
656    $data = Rex::Logger::masq( "%s", $data ) if $key eq 'sudo_password';
657    $data ||= "";
658
659    Rex::Logger::debug("$key => [[$data]]");
660  }
661
662  $auth->{public_key} = resolv_path( $auth->{public_key}, 1 )
663    if ( $auth->{public_key} );
664  $auth->{private_key} = resolv_path( $auth->{private_key}, 1 )
665    if ( $auth->{private_key} );
666
667  my $profiler = Rex::Profiler->new;
668
669  # task specific auth rules over all
670  my %connect_hash = %{$auth};
671  $connect_hash{server} = $server;
672
673  # need to get rid of this
674  Rex::push_connection(
675    {
676      conn     => $self->connection,
677      ssh      => $self->connection->get_connection_object,
678      server   => $server,
679      cache    => Rex::Interface::Cache->create(),
680      task     => [],
681      profiler => $profiler,
682      reporter => Rex::Report->create( Rex::Config->get_report_type ),
683      notify   => Rex::Notify->new(),
684    }
685  );
686
687  push @{ Rex::get_current_connection()->{task} }, $self;
688
689  $profiler->start("connect");
690  eval {
691    $self->connection->connect(%connect_hash);
692    1;
693  } or do {
694    if ( !defined Rex::Config->get_fallback_auth ) {
695      croak $@;
696    }
697  };
698  $profiler->end("connect");
699
700  if ( !$self->connection->is_connected ) {
701    Rex::pop_connection();
702    croak("Couldn't connect to $server.");
703  }
704  elsif ( !$self->connection->is_authenticated ) {
705    Rex::pop_connection();
706    my $message =
707      "Couldn't authenticate against $server. It may be caused by one or more of:\n";
708    $message .= " - wrong username, password, key or passphrase\n";
709    $message .= " - changed remote host key\n";
710    $message .= " - root is not permitted to login over SSH\n"
711      if ( $connect_hash{user} eq 'root' );
712
713    if ( !exists $override{auth} ) {
714      my $fallback_auth = Rex::Config->get_fallback_auth;
715      if ( ref $fallback_auth eq "ARRAY" ) {
716        my $ret_eval;
717        for my $fallback_a ( @{$fallback_auth} ) {
718          $ret_eval = eval { $self->connect( $server, auth => $fallback_a ); };
719        }
720
721        return $ret_eval if $ret_eval;
722      }
723    }
724
725    croak($message);
726  }
727  else {
728    Rex::Logger::debug("Successfully authenticated on $server.")
729      if ( $self->connection->get_connection_type ne "Local" );
730    $self->{"__was_authenticated"} = 1;
731  }
732
733  $self->run_hook( \$server, "around" );
734
735  return 1;
736}
737
738=head2 disconnect
739
740Disconnect from the current connection.
741
742=cut
743
744sub disconnect {
745  my ( $self, $server ) = @_;
746
747  $self->run_hook( \$server, "around", 1 );
748  $self->connection->disconnect;
749
750  my %args = Rex::Args->getopts;
751
752  if ( defined $args{'d'} && $args{'d'} > 2 ) {
753    Rex::Commands::profiler()->report;
754  }
755
756  delete $self->{connection};
757
758  pop @{ Rex::get_current_connection()->{task} };
759
760  # need to get rid of this
761  Rex::pop_connection();
762
763  $self->run_hook( \$server, "after" );
764}
765
766=head2 get_data
767
768Dump task data.
769
770=cut
771
772sub get_data {
773  my ($self) = @_;
774
775  return {
776    func            => $self->{func},
777    server          => $self->{server},
778    desc            => $self->{desc},
779    no_ssh          => $self->{no_ssh},
780    hidden          => $self->{hidden},
781    auth            => $self->{auth},
782    before          => $self->{before},
783    after           => $self->{after},
784    around          => $self->{around},
785    name            => $self->{name},
786    executor        => $self->{executor},
787    connection_type => $self->{connection_type},
788    opts            => $self->{opts},
789    args            => $self->{args},
790  };
791}
792
793=head2 run($server, %options)
794
795Run the task on C<$server>, with C<%options>.
796
797=cut
798
799sub run {
800  return pre_40_run(@_) unless ref $_[0];
801
802  my ( $self, $server, %options ) = @_;
803
804  $options{opts}   ||= { $self->get_opts };
805  $options{args}   ||= [ $self->get_args ];
806  $options{params} ||= $options{opts};
807
808  if ( !ref $server ) {
809    $server = Rex::Group::Entry::Server->new( name => $server );
810  }
811
812  if ( !$_[1] ) {
813
814    # run is called without any server.
815    # so just connect to any servers.
816    return Rex::TaskList->create()->run( $self, %options );
817  }
818
819  # this is a method call
820  # so run the task
821
822  # TODO: refactor complete task calling
823  #       direct call with function and normal task call
824
825  my ( $in_transaction, $start_time );
826
827  $start_time = time;
828
829  if ( $server ne "<func>" ) {
830
831    # this is _not_ a task call via function syntax.
832
833    $in_transaction = $options{in_transaction};
834
835    eval { $self->connect($server) };
836    if ($@) {
837      my $error = $@;
838      $self->{"__was_authenticated"} = 0;
839      $self->run_hook( \$server, "after" );
840      die $error;
841    }
842
843    if ( Rex::Args->is_opt("c") ) {
844
845      # get and cache all os info
846      if ( !Rex::get_cache()->load() ) {
847        Rex::Logger::debug("No cache found, need to collect new data.");
848        $server->gather_information;
849      }
850    }
851
852    if ( !$server->test_perl ) {
853      Rex::Logger::info(
854        "There is no perl interpreter found on this system. "
855          . "Some commands may not work. Sudo won't work.",
856        "warn"
857      );
858      sleep 3;
859    }
860
861  }
862  else {
863# we need to push the connection information of the last task onto this task object
864# if we don't do this, the task doesn't have any information of the current connection when called like a function.
865# See: #1091
866    $self->set_connection(
867      Rex::get_current_connection()->{task}->[-1]->connection )
868      if Rex::get_current_connection()->{task}->[-1];
869    push @{ Rex::get_current_connection()->{task} }, $self;
870  }
871
872  # execute code
873  my @ret;
874  my $wantarray = wantarray;
875
876  eval {
877    $self->set_opts( %{ $options{params} } )
878      if ref $options{params} eq "HASH";
879    if ($wantarray) {
880      @ret = $self->executor->exec( $options{params}, $options{args} );
881    }
882    else {
883      $ret[0] = $self->executor->exec( $options{params}, $options{args} );
884    }
885    my $notify = Rex::get_current_connection()->{notify};
886    $notify->run_postponed();
887  } or do {
888    if ($@) {
889      my $error = $@;
890
891      Rex::get_current_connection()->{reporter}
892        ->report_resource_failed( message => $error );
893
894      Rex::get_current_connection()->{reporter}->report_task_execution(
895        failed     => 1,
896        start_time => $start_time,
897        end_time   => time,
898        message    => $error,
899      );
900
901      Rex::get_current_connection()->{reporter}->write_report();
902
903      pop @{ Rex::get_current_connection()->{task} };
904      die($error);
905    }
906  };
907
908  if ( $server ne "<func>" ) {
909    if ( Rex::Args->is_opt("c") ) {
910
911      # get and cache all os info
912      Rex::get_cache()->save();
913    }
914
915    Rex::get_current_connection()->{reporter}->report_task_execution(
916      failed     => 0,
917      start_time => $start_time,
918      end_time   => time,
919    );
920
921    Rex::get_current_connection()->{reporter}->write_report();
922
923    if ($in_transaction) {
924      $self->run_hook( \$server, "around", 1 );
925      $self->run_hook( \$server, "after" );
926    }
927    else {
928      $self->disconnect($server);
929    }
930  }
931  else {
932    pop @{ Rex::get_current_connection()->{task} };
933  }
934
935  if ($wantarray) {
936    return @ret;
937  }
938  else {
939    return $ret[0];
940  }
941}
942
943sub pre_40_run {
944  my ( $class, $task_name, $server_overwrite, $params ) = @_;
945
946  # static calls to this method are deprecated
947  Rex::deprecated( "Rex::Task->run()", "0.40" );
948
949  my $tasklist = Rex::TaskList->create;
950  my $task     = $tasklist->get_task($task_name);
951
952  $task->set_server($server_overwrite) if $server_overwrite;
953  $tasklist->run( $task, params => $params );
954}
955
956=head2 modify_task($task, $key => $value)
957
958Modify C<$task>, by setting C<$key> to C<$value>.
959
960=cut
961
962sub modify_task {
963  my $class = shift;
964  my $task  = shift;
965  my $key   = shift;
966  my $value = shift;
967
968  Rex::TaskList->create()->get_task($task)->modify( $key => $value );
969}
970
971=head2 is_task
972
973Returns true(1) if the passed object is a task.
974
975=cut
976
977sub is_task {
978  my ( $class, $task ) = @_;
979  return Rex::TaskList->create()->is_task($task);
980}
981
982=head2 get_tasks
983
984Returns list of tasks.
985
986=cut
987
988sub get_tasks {
989  my ( $class, @tmp ) = @_;
990  return Rex::TaskList->create()->get_tasks(@tmp);
991}
992
993=head2 get_desc
994
995Returns description of task.
996
997=cut
998
999sub get_desc {
1000  my ( $class, @tmp ) = @_;
1001  return Rex::TaskList->create()->get_desc(@tmp);
1002}
1003
1004=head2 exit_on_connect_fail
1005
1006Returns true if rex should exit on connect failure.
1007
1008=cut
1009
1010sub exit_on_connect_fail {
1011  my ($self) = @_;
1012  return $self->{exit_on_connect_fail};
1013}
1014
1015=head2 set_exit_on_connect_fail
1016
1017Sets if rex should exit on connect failure.
1018
1019=cut
1020
1021sub set_exit_on_connect_fail {
1022  my ( $self, $exit ) = @_;
1023  $self->{exit_on_connect_fail} = $exit;
1024}
1025
1026=head2 get_args
1027
1028Returns arguments of task.
1029
1030=cut
1031
1032sub get_args {
1033  my ($self) = @_;
1034  @{ $self->{args} || [] };
1035}
1036
1037=head2 get_opts
1038
1039Returns options of task.
1040
1041=cut
1042
1043sub get_opts {
1044  my ($self) = @_;
1045  %{ $self->{opts} || {} };
1046}
1047
1048=head2 set_args
1049
1050Sets arguments for task.
1051
1052=cut
1053
1054sub set_args {
1055  my ( $self, @args ) = @_;
1056  $self->{args} = \@args;
1057}
1058
1059=head2 set_opt
1060
1061Sets an option for task.
1062
1063=cut
1064
1065sub set_opt {
1066  my ( $self, $key, $value ) = @_;
1067  $self->{opts}->{$key} = $value;
1068}
1069
1070=head2 set_opts
1071
1072Sets options for task.
1073
1074=cut
1075
1076sub set_opts {
1077  my ( $self, %opts ) = @_;
1078  $self->{opts} = \%opts;
1079}
1080
1081=head2 clone
1082
1083Clones a task.
1084
1085=cut
1086
1087sub clone {
1088  my $self = shift;
1089  return Rex::Task->new( %{ $self->get_data } );
1090}
1091
10921;
1093