File Coverage

lib/Rex/Commands/Pkg.pm
Criterion Covered Total %
statement 50 259 19.3
branch 0 110 0.0
condition 0 29 0.0
subroutine 17 29 58.6
pod 9 10 90.0
total 76 437 17.3


line stmt bran cond sub pod time code
1             #
2             # (c) Jan Gehring
3             #
4              
5             =head1 NAME
6              
7             Rex::Commands::Pkg - Install/Remove Software packages
8              
9             =head1 DESCRIPTION
10              
11             With this module you can install packages and files.
12              
13             =head1 SYNOPSIS
14              
15             pkg "somepkg",
16             ensure => "present";
17             pkg "somepkg",
18             ensure => "latest",
19             on_change => sub {
20             say "package was updated.";
21             service someservice => "restart";
22             };
23             pkg "somepkg",
24             ensure => "absent";
25              
26             =head1 EXPORTED FUNCTIONS
27              
28             =cut
29              
30             package Rex::Commands::Pkg;
31              
32 32     32   573 use v5.12.5;
  32         186  
33 32     32   237 use warnings;
  32         134  
  32         2036  
34              
35             our $VERSION = '1.14.2.3'; # TRIAL VERSION
36              
37 32     32   466 use Rex::Pkg;
  32         110  
  32         400  
38 32     32   1062 use Rex::Logger;
  32         129  
  32         174  
39 32     32   1103 use Rex::Template;
  32         83  
  32         284  
40 32     32   1325 use Rex::Commands::File;
  32         102  
  32         291  
41 32     32   246 use Rex::Commands::Fs;
  32         102  
  32         256  
42 32     32   245 use Rex::Commands::Gather;
  32         118  
  32         210  
43 32     32   330 use Rex::Hardware;
  32         158  
  32         278  
44 32     32   1153 use Rex::Commands::MD5;
  32         94  
  32         450  
45 32     32   354 use Rex::Commands::Upload;
  32         143  
  32         292  
46 32     32   222 use Rex::Config;
  32         78  
  32         353  
47 32     32   249 use Rex::Commands;
  32         127  
  32         240  
48 32     32   284 use Rex::Hook;
  32         117  
  32         2666  
49              
50 32     32   273 use Data::Dumper;
  32         126  
  32         2217  
51              
52             require Rex::Exporter;
53              
54 32     32   281 use base qw(Rex::Exporter);
  32         146  
  32         2943  
55 32     32   320 use vars qw(@EXPORT);
  32         110  
  32         89227  
56              
57             @EXPORT =
58             qw(install update remove update_system installed_packages is_installed update_package_db repository package_provider_for pkg);
59              
60             =head2 pkg($package [, %options])
61              
62             Since: 0.45
63              
64             Use this resource to install or update a package. This resource will generate reports.
65              
66             pkg "httpd",
67             ensure => "latest", # ensure that the newest version is installed (auto-update)
68             on_change => sub { say "package was installed/updated"; };
69              
70             pkg "httpd",
71             ensure => "absent"; # remove the package
72              
73             pkg "httpd",
74             ensure => "present"; # ensure that some version is installed (no auto-update)
75              
76             pkg "httpd",
77             ensure => "2.4.6"; # ensure that version 2.4.6 is installed
78              
79             pkg "apache-server", # with a custom resource name
80             package => "httpd",
81             ensure => "present";
82              
83             =cut
84              
85             sub pkg {
86 0     0 1   my ( $package, %option ) = @_;
87              
88 0 0 0       if ( exists $option{package} && ref $option{package} eq "ARRAY" ) {
89 0           die "The `packageĀ“ option can't be an array.";
90             }
91              
92 0           my $res_name = $package;
93              
94 0 0         if ( exists $option{package} ) {
95 0           $package = $option{package};
96             }
97              
98 0   0       $option{ensure} ||= "present";
99              
100 0 0         my @package_list = ref $package eq "ARRAY" ? @{$package} : ($package);
  0            
101              
102 0           foreach my $candidate ( sort @package_list ) {
103             Rex::get_current_connection()->{reporter}->report_resource_start(
104 0 0         type => "pkg",
105             name => ( ref $res_name eq "ARRAY" ? $candidate : $res_name )
106             );
107             }
108              
109 0           my $pkg = Rex::Pkg->get;
110 0           my @old_installed = $pkg->get_installed;
111              
112 0 0         if ( $option{ensure} eq "latest" ) {
    0          
    0          
    0          
113 0           &update( package => $package, \%option );
114             }
115             elsif ( $option{ensure} =~ m/^(present|installed)$/ ) {
116 0           &install( package => $package, \%option );
117             }
118             elsif ( $option{ensure} eq "absent" ) {
119 0           &remove( package => $package );
120             }
121             elsif ( $option{ensure} =~ m/^\d/ ) {
122              
123             # looks like a version
124 0           &install( package => $package, { version => $option{ensure} } );
125             }
126             else {
127 0           die("Unknown ensure parameter: $option{ensure}.");
128             }
129              
130 0           my @new_installed = $pkg->get_installed;
131 0           my @modifications =
132             $pkg->diff_package_list( \@old_installed, \@new_installed );
133              
134 0 0 0       if ( exists $option{on_change}
      0        
135             && ref $option{on_change} eq "CODE"
136             && scalar @modifications > 0 )
137             {
138 0           $option{on_change}->( $package, %option );
139             }
140              
141 0           foreach my $candidate ( reverse sort @package_list ) {
142              
143 0           my %report_args = ( changed => 0 );
144              
145 0 0         if ( my ($change) = grep { $candidate eq $_->{name} } @modifications ) {
  0            
146 0           $report_args{changed} = 1;
147              
148 0           my ($old_package) = grep { $_->{name} eq $change->{name} } @old_installed;
  0            
149 0           my ($new_package) = grep { $_->{name} eq $change->{name} } @new_installed;
  0            
150              
151 0 0         if ( $change->{action} eq "updated" ) {
    0          
    0          
152             $report_args{message} =
153 0           "Package $change->{name} updated $old_package->{version} -> $new_package->{version}";
154             }
155             elsif ( $change->{action} eq "installed" ) {
156             $report_args{message} =
157 0           "Package $change->{name} installed in version $new_package->{version}";
158             }
159             elsif ( $change->{action} eq "removed" ) {
160 0           $report_args{message} = "Package $change->{name} removed.";
161             }
162             }
163              
164 0           Rex::get_current_connection()->{reporter}->report(%report_args);
165              
166             Rex::get_current_connection()->{reporter}->report_resource_end(
167 0 0         type => "pkg",
168             name => ( ref $res_name eq "ARRAY" ? $candidate : $res_name )
169             );
170             }
171             }
172              
173             =head2 install($type, $data, $options)
174              
175             The install function can install packages (for CentOS, OpenSuSE and Debian) and files.
176              
177             If you need reports, please use the pkg() resource.
178              
179             =over 8
180              
181             =item installing a package (This is only supported on CentOS, OpenSuSE and Debian systems.)
182              
183             task "prepare", "server01", sub {
184             install package => "perl";
185              
186             # or if you have to install more packages.
187             install package => [
188             "perl",
189             "ntp",
190             "dbus",
191             "hal",
192             "sudo",
193             "vim",
194             ];
195             };
196              
197             =item installing a file
198              
199             This is deprecated since 0.9. Please use L I instead.
200              
201             task "prepare", "server01", sub {
202             install file => "/etc/passwd", {
203             source => "/export/files/etc/passwd",
204             owner => "root",
205             group => "root",
206             mode => 644,
207             };
208             };
209              
210             =item installing a file and do something if the file was changed.
211              
212             task "prepare", "server01", sub {
213             install file => "/etc/httpd/apache2.conf", {
214             source => "/export/files/etc/httpd/apache2.conf",
215             owner => "root",
216             group => "root",
217             mode => 644,
218             on_change => sub { say "File was modified!"; }
219             };
220             };
221              
222             =item installing a file from a template.
223              
224             task "prepare", "server01", sub {
225             install file => "/etc/httpd/apache2.tpl", {
226             source => "/export/files/etc/httpd/apache2.conf",
227             owner => "root",
228             group => "root",
229             mode => 644,
230             on_change => sub { say "File was modified!"; },
231             template => {
232             greeting => "hello",
233             name => "Ben",
234             },
235             };
236             };
237              
238              
239             =back
240              
241             This function supports the following L:
242              
243             =over 4
244              
245             =item before
246              
247             This gets executed before anything is done. All original parameters are passed to it.
248              
249             The return value of this hook overwrites the original parameters of the function-call.
250              
251             =item before_change
252              
253             This gets executed right before the new package is installed. All original parameters are passed to it.
254              
255             This hook is only available for package installations. If you need file hooks, you have to use the L function.
256              
257             =item after_change
258              
259             This gets executed right after the new package was installed. All original parameters, and the fact of change (C<{ changed => TRUE|FALSE }>) are passed to it.
260              
261             This hook is only available for package installations. If you need file hooks, you have to use the L function.
262              
263             =item after
264              
265             This gets executed right before the C function returns. All original parameters, and any returned results are passed to it.
266              
267             =back
268              
269             =cut
270              
271             sub install {
272              
273 0 0   0 1   if ( !@_ ) {
274 0           return "install";
275             }
276              
277             #### check and run before hook
278 0           my @orig_params = @_;
279             eval {
280 0           my @new_args = Rex::Hook::run_hook( install => "before", @_ );
281 0 0         if (@new_args) {
282 0           @_ = @new_args;
283             }
284 0           1;
285 0 0         } or do {
286 0           die("Before-Hook failed. Canceling install() action: $@");
287             };
288             ##############################
289              
290 0           my $type = shift;
291 0           my $package = shift;
292 0           my $option;
293             my $__ret;
294              
295 0 0         if ( $type eq "file" ) {
    0          
296              
297 0 0         if ( ref( $_[0] ) eq "HASH" ) {
298 0           $option = shift;
299             }
300             else {
301 0           $option = {@_};
302             }
303              
304 0           Rex::Logger::debug(
305             "The install file => ... call is deprecated. Please use 'file' instead.");
306 0           Rex::Logger::debug("This directive will be removed with (R)?ex 2.0");
307 0           Rex::Logger::debug(
308             "See http://rexify.org/api/Rex/Commands/File.pm for more information.");
309              
310 0           my $source = $option->{"source"};
311 0 0         my $need_md5 = ( $option->{"on_change"} ? 1 : 0 );
312 0   0 0     my $on_change = $option->{"on_change"} || sub { };
313 0           my $__ret;
314              
315 0           my ( $new_md5, $old_md5 ) = ( "", "" );
316              
317 0 0         if ( $source =~ m/\.tpl$/ ) {
318              
319             # das ist ein template
320              
321 0           my $content = eval { local ( @ARGV, $/ ) = ($source); <>; };
  0            
  0            
322              
323 0           my $vars = $option->{"template"};
324 0 0         my %merge1 = %{ $vars || {} };
  0            
325 0           my %merge2 = Rex::Hardware->get(qw/ All /);
326 0           my %template_vars = ( %merge1, %merge2 );
327              
328 0 0         if ($need_md5) {
329 0           eval { $old_md5 = md5($package); };
  0            
330             }
331              
332 0           my $fh = file_write($package);
333 0           $fh->write(
334             Rex::Config->get_template_function()->( $content, \%template_vars ) );
335 0           $fh->close;
336              
337 0 0         if ($need_md5) {
338 0           eval { $new_md5 = md5($package); };
  0            
339             }
340              
341             }
342             else {
343              
344 0           my $source = Rex::Helper::Path::get_file_path( $source, caller() );
345 0           my $content = eval { local ( @ARGV, $/ ) = ($source); <>; };
  0            
  0            
346              
347 0           my $local_md5 = "";
348 0 0         if ( $option->{force} ) {
349 0           upload $source, $package;
350             }
351             else {
352 0           eval {
353 0           $old_md5 = md5($package);
354 0           chomp $old_md5;
355             };
356              
357             LOCAL {
358 0     0     $local_md5 = md5($source);
359 0           };
360              
361 0 0         unless ( $local_md5 eq $old_md5 ) {
362 0           Rex::Logger::debug(
363             "MD5 is different $local_md5 -> $old_md5 (uploading)");
364 0           upload $source, $package;
365             }
366             else {
367 0           Rex::Logger::debug("MD5 is equal. Not uploading $source -> $package");
368             }
369              
370 0           eval { $new_md5 = md5($package); };
  0            
371             }
372             }
373              
374 0 0         if ( exists $option->{"owner"} ) {
375 0           chown $option->{"owner"}, $package;
376             }
377              
378 0 0         if ( exists $option->{"group"} ) {
379 0           chgrp $option->{"group"}, $package;
380             }
381              
382 0 0         if ( exists $option->{"mode"} ) {
383 0           chmod $option->{"mode"}, $package;
384             }
385              
386 0 0         if ($need_md5) {
387 0 0 0       unless ( $old_md5 && $new_md5 && $old_md5 eq $new_md5 ) {
      0        
388 0   0       $old_md5 ||= "";
389 0   0       $new_md5 ||= "";
390              
391 0           Rex::Logger::debug(
392             "File $package has been changed... Running on_change");
393 0           Rex::Logger::debug("old: $old_md5");
394 0           Rex::Logger::debug("new: $new_md5");
395              
396 0           &$on_change;
397             }
398             }
399              
400             }
401              
402             elsif ( $type eq "package" ) {
403              
404 0 0         if ( ref( $_[0] ) eq "HASH" ) {
    0          
405 0           $option = shift;
406             }
407             elsif ( $_[0] ) {
408 0           $option = {@_};
409             }
410              
411 0           my $pkg;
412              
413 0           $pkg = Rex::Pkg->get;
414              
415 0 0         if ( !ref($package) ) {
416 0           $package = [$package];
417             }
418              
419 0           my $changed = 0;
420              
421             # if we're being asked to install a single package
422 0 0         if ( @{$package} == 1 ) {
  0            
423 0           my $pkg_to_install = shift @{$package};
  0            
424 0 0         unless ( $pkg->is_installed( $pkg_to_install, $option ) ) {
425 0           Rex::Logger::info("Installing $pkg_to_install.");
426              
427             #### check and run before_change hook
428 0           Rex::Hook::run_hook( install => "before_change", @orig_params );
429             ##############################
430              
431 0           $pkg->install( $pkg_to_install, $option );
432 0           $changed = 1;
433              
434             #### check and run after_change hook
435 0           Rex::Hook::run_hook(
436             install => "after_change",
437             @orig_params, { changed => $changed }
438             );
439             ##############################
440             }
441             }
442             else {
443 0           my @pkgCandidates;
444 0           for my $pkg_to_install ( @{$package} ) {
  0            
445 0 0         unless ( $pkg->is_installed( $pkg_to_install, $option ) ) {
446 0           push @pkgCandidates, $pkg_to_install;
447             }
448             }
449              
450 0 0         if (@pkgCandidates) {
451 0           Rex::Logger::info("Installing @pkgCandidates");
452 0           $pkg->bulk_install( \@pkgCandidates, $option ); # here, i think $option is useless in its current form.
453 0           $changed = 1;
454             }
455             }
456              
457 0 0         if ( Rex::Config->get_do_reporting ) {
458 0           $__ret = { changed => $changed };
459             }
460              
461             }
462             else {
463             # unknown type, be a package
464 0           install( "package", $type, $package, @_ );
465              
466 0 0         if ( Rex::Config->get_do_reporting ) {
467 0           $__ret = { skip => 1 };
468             }
469             }
470              
471             #### check and run after hook
472 0           Rex::Hook::run_hook( install => "after", @orig_params, $__ret );
473             ##############################
474              
475 0           return $__ret;
476              
477             }
478              
479             sub update {
480              
481 0     0 0   my ( $type, $package, $option ) = @_;
482              
483 0 0         if ( $type eq "package" ) {
484 0           my $pkg;
485              
486 0           $pkg = Rex::Pkg->get;
487              
488 0 0         if ( !ref($package) ) {
489 0           $package = [$package];
490             }
491              
492 0           for my $pkg_to_install ( @{$package} ) {
  0            
493 0           Rex::Logger::info("Updating $pkg_to_install.");
494 0           $pkg->update( $pkg_to_install, $option );
495             }
496              
497             }
498             else {
499 0           update( "package", @_ );
500             }
501              
502             }
503              
504             =head2 remove($type, $package, $options)
505              
506             This function will remove the given package from a system.
507              
508             task "cleanup", "server01", sub {
509             remove package => "vim";
510             };
511              
512             =cut
513              
514             sub remove {
515              
516 0     0 1   my ( $type, $package, $option ) = @_;
517              
518 0 0         if ( $type eq "package" ) {
519              
520 0           my $pkg = Rex::Pkg->get;
521 0 0         unless ( ref($package) eq "ARRAY" ) {
522 0           $package = ["$package"];
523             }
524              
525 0           for my $_pkg ( @{$package} ) {
  0            
526 0 0         if ( $pkg->is_installed($_pkg) ) {
527 0           Rex::Logger::info("Removing $_pkg.");
528 0           $pkg->remove( $_pkg, $option );
529 0           $pkg->purge( $_pkg, $option );
530             }
531             else {
532 0           Rex::Logger::info("$_pkg is not installed.");
533             }
534             }
535              
536             }
537              
538             else {
539              
540             #Rex::Logger::info("$type not supported.");
541             #die("remove $type not supported");
542             # no type given, assume package
543 0           remove( "package", $type, $option );
544              
545             }
546              
547             }
548              
549             =head2 update_system
550              
551             This function does a complete system update.
552              
553             For example I or I.
554              
555             task "update-system", "server1", sub {
556             update_system;
557             };
558              
559             If you want to get the packages that where updated, you can use the I hook.
560              
561             task "update-system", "server1", sub {
562             update_system
563             on_change => sub {
564             my (@modified_packages) = @_;
565             for my $pkg (@modified_packages) {
566             say "Name: $pkg->{name}";
567             say "Version: $pkg->{version}";
568             say "Action: $pkg->{action}"; # some of updated, installed or removed
569             }
570             };
571             };
572              
573             Options for I
574              
575             =over 4
576              
577             =item update_metadata
578              
579             Set to I if the package metadata should be updated. Since 1.5 default to I if possible. Before 1.5 it depends on the package manager.
580              
581             =item update_package
582              
583             Set to I if you want to update the packages. Default is I.
584              
585             =item dist_upgrade
586              
587             Set to I if you want to run a dist-upgrade if your distribution supports it. Default is I.
588              
589             =back
590              
591             =cut
592              
593             sub update_system {
594 0     0 1   my $pkg = Rex::Pkg->get;
595 0           my (%option) = @_;
596              
597             # safe the currently installed packages, so that we can compare
598             # the package db for changes
599 0           my @old_installed = $pkg->get_installed;
600              
601 0           eval { $pkg->update_system(%option); };
  0            
602 0 0         Rex::Logger::info( "An error occurred for update_system: $@", "warn" ) if $@;
603              
604 0           my @new_installed = $pkg->get_installed;
605              
606 0           my @modifications =
607             $pkg->diff_package_list( \@old_installed, \@new_installed );
608              
609 0 0         if ( scalar @modifications > 0 ) {
610              
611             # there where some changes in the package database
612 0 0 0       if ( exists $option{on_change} && ref $option{on_change} eq "CODE" ) {
613              
614             # run the on_change hook
615 0           $option{on_change}->(@modifications);
616             }
617             }
618             }
619              
620             =head2 installed_packages
621              
622             This function returns all installed packages and their version.
623              
624             task "get-installed", "server1", sub {
625              
626             for my $pkg (installed_packages()) {
627             say "name : " . $pkg->{"name"};
628             say " version: " . $pkg->{"version"};
629             }
630              
631             };
632              
633             =cut
634              
635             sub installed_packages {
636 0     0 1   my $pkg = Rex::Pkg->get;
637 0           return $pkg->get_installed;
638             }
639              
640             =head2 is_installed
641              
642             This function tests if $package is installed. Returns 1 if true. 0 if false.
643              
644             task "isinstalled", "server01", sub {
645             if( is_installed("rex") ) {
646             say "Rex is installed";
647             }
648             else {
649             say "Rex is not installed";
650             }
651             };
652              
653             =cut
654              
655             sub is_installed {
656 0     0 1   my $package = shift;
657 0           my $pkg = Rex::Pkg->get;
658 0           return $pkg->is_installed($package);
659             }
660              
661             =head2 update_package_db
662              
663             This function updates the local package database. For example, on CentOS it will execute I.
664              
665             task "update-pkg-db", "server1", "server2", sub {
666             update_package_db;
667             install package => "apache2";
668             };
669              
670             =cut
671              
672             sub update_package_db {
673 0     0 1   my $pkg = Rex::Pkg->get;
674 0           $pkg->update_pkg_db();
675             }
676              
677             =head2 repository($action, %data)
678              
679             Add or remove a repository from the package manager.
680              
681             For Debian: If you have no source repository, or if you don't want to add it, just remove the I parameter.
682              
683             task "add-repo", "server1", "server2", sub {
684             repository "add" => "repository-name",
685             url => "http://rex.linux-files.org/debian/squeeze",
686             key_url => "http://rex.linux-files.org/DPKG-GPG-KEY-REXIFY-REPO"
687             distro => "squeeze",
688             repository => "rex",
689             source => 1;
690             };
691              
692             To specify a key from a file use key_file => '/tmp/mykeyfile'.
693              
694             To use a keyserver use key_server and key_id.
695              
696             For ALT Linux: If repository is unsigned, just remove the I parameter.
697              
698             task "add-repo", "server1", "server2", sub {
699             repository "add" => "altlinux-sisyphus",
700             url => "ftp://ftp.altlinux.org/pub/distributions/ALTLinux/Sisyphus",
701             sign_key => "alt",
702             arch => "noarch, x86_64",
703             repository => "classic";
704             };
705              
706             For CentOS, Mageia and SuSE only the name and the url are needed.
707              
708             task "add-repo", "server1", "server2", sub {
709             repository add => "repository-name",
710             url => 'http://rex.linux-files.org/CentOS/$releasever/rex/$basearch/';
711              
712             };
713              
714             To remove a repository just delete it with its name.
715              
716             task "rm-repo", "server1", sub {
717             repository remove => "repository-name";
718             };
719              
720             You can also use one call to repository to add repositories on multiple platforms:
721              
722             task "add-repo", "server1", "server2", sub {
723             repository add => myrepo => {
724             Ubuntu => {
725             url => "http://foo.bar/repo",
726             distro => "precise",
727             repository => "foo",
728             },
729             Debian => {
730             url => "http://foo.bar/repo",
731             distro => "squeeze",
732             repository => "foo",
733             },
734             CentOS => {
735             url => "http://foo.bar/repo",
736             },
737             };
738             };
739              
740              
741             =cut
742              
743             sub repository {
744 0     0 1   my ( $action, $name, @__data ) = @_;
745              
746 0           my %data;
747              
748 0 0         if ( ref( $__data[0] ) ) {
749 0 0         if ( !exists $__data[0]->{ get_operating_system() } ) {
750 0 0         if ( exists $__data[0]->{default} ) {
751 0           %data = $__data[0]->{default};
752             }
753             else {
754 0           die(
755             "No repository information found for os: " . get_operating_system() );
756             }
757             }
758             else {
759 0           %data = %{ $__data[0]->{ get_operating_system() } };
  0            
760             }
761             }
762             else {
763 0           %data = @__data;
764             }
765              
766 0           my $pkg = Rex::Pkg->get;
767              
768 0           $data{"name"} = $name;
769              
770 0           my $ret;
771 0 0 0       if ( $action eq "add" ) {
    0          
772 0           $ret = $pkg->add_repository(%data);
773             }
774             elsif ( $action eq "remove" || $action eq "delete" ) {
775 0           $ret = $pkg->rm_repository($name);
776             }
777              
778 0 0         if ( exists $data{after} ) {
779 0           $data{after}->();
780             }
781              
782 0           return $ret;
783             }
784              
785             =head2 package_provider_for $os => $type;
786              
787             To set another package provider as the default, use this function.
788              
789             user "root";
790              
791             group "db" => "db[01..10]";
792             package_provider_for SunOS => "blastwave";
793              
794             task "prepare", group => "db", sub {
795             install package => "vim";
796             };
797              
798             This example will install I on every db server. If the server is a Solaris (SunOS) it will use the I Repositories.
799              
800             =cut
801              
802             sub package_provider_for {
803 0     0 1   my ( $os, $provider ) = @_;
804 0           Rex::Config->set( "package_provider", { $os => $provider } );
805             }
806              
807             1;