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
565
use v5.12.5;
32
211
33
32
32
314
use warnings;
32
120
32
1863
34
35
our $VERSION = '1.14.2.2'; # TRIAL VERSION
36
37
32
32
440
use Rex::Pkg;
32
117
32
434
38
32
32
1107
use Rex::Logger;
32
130
32
156
39
32
32
1059
use Rex::Template;
32
80
32
337
40
32
32
806
use Rex::Commands::File;
32
83
32
285
41
32
32
311
use Rex::Commands::Fs;
32
125
32
268
42
32
32
277
use Rex::Commands::Gather;
32
192
32
236
43
32
32
285
use Rex::Hardware;
32
146
32
323
44
32
32
1245
use Rex::Commands::MD5;
32
153
32
602
45
32
32
360
use Rex::Commands::Upload;
32
123
32
242
46
32
32
296
use Rex::Config;
32
126
32
247
47
32
32
279
use Rex::Commands;
32
90
32
332
48
32
32
449
use Rex::Hook;
32
170
32
2450
49
50
32
32
284
use Data::Dumper;
32
105
32
2267
51
52
require Rex::Exporter;
53
54
32
32
301
use base qw(Rex::Exporter);
32
102
32
3156
55
32
32
337
use vars qw(@EXPORT);
32
131
32
92091
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;