File Coverage

blib/lib/Server/Module/Comparison.pm
Criterion Covered Total %
statement 104 151 68.8
branch 28 42 66.6
condition 2 3 66.6
subroutine 15 20 75.0
pod 5 8 62.5
total 154 224 68.7


line stmt bran cond sub pod time code
1 3     3   267407 use strictures 2;
  3         3353  
  3         107  
2             package Server::Module::Comparison;
3              
4             # ABSTRACT: check perl module versions installed on servers.
5              
6 3     3   2497 use Path::Tiny;
  3         23755  
  3         156  
7 3     3   1390 use Moo;
  3         25112  
  3         15  
8 3     3   4609 use Types::Standard -types;
  3         142166  
  3         36  
9 3     3   9591 use Capture::Tiny qw/capture/;
  3         58065  
  3         173  
10 3     3   1328 use failures qw/module::comparison/;
  3         7853  
  3         16  
11 3     3   1596 use Perl::Version;
  3         5461  
  3         3355  
12              
13             our $VERSION = '0.011';
14              
15             has perl_path => (is => 'ro', isa => Str, default => '');
16             has modules => (is => 'ro', isa => ArrayRef);
17              
18             sub FromModuleList
19             {
20 0     0 1 0 my $filename = shift;
21 0         0 my $extra_params = shift;
22 0 0       0 $extra_params = {} unless defined $extra_params;
23 0         0 my @lines;
24 0 0       0 if($filename eq '-')
25             {
26 0         0 @lines = ;
27             }
28             else
29             {
30 0         0 @lines = path($filename)->lines_utf8;
31             }
32 0         0 my @modules = map { chomp; $_ } grep { !/^\s*$/ } @lines;
  0         0  
  0         0  
  0         0  
33 0         0 return Server::Module::Comparison->new({ %$extra_params, modules => \@modules });
34             }
35              
36             sub _mversion_command
37             {
38 1     1   2 my $self = shift;
39 1         2 my $command = 'mversion';
40 1 50       6 if($self->perl_path)
41             {
42 1         8 $command = path($self->perl_path)->child($command);
43             }
44 1         90 return [$command, '-f', @{$self->modules}, '2>&1'];
  1         4  
45             }
46              
47             sub check_container
48             {
49 0     0 1 0 my $self = shift;
50 0         0 my $container = shift;
51 0         0 my $cmd = [qw/docker run --rm -i/, $container, @{$self->_mversion_command}];
  0         0  
52 0         0 return $self->_run_mversion($cmd);
53             }
54              
55             sub check_ssh_server
56             {
57 0     0 1 0 my $self = shift;
58 0         0 my $server = shift;
59 0         0 my $cmd = ['ssh', $server, @{$self->_mversion_command}];
  0         0  
60 0         0 return $self->_run_mversion($cmd);
61             }
62              
63             sub check_local
64             {
65 1     1 1 2509 my $self = shift;
66 1         4 my $cmd = $self->_mversion_command;
67 1         5 return $self->_run_mversion($cmd);
68             }
69              
70             sub identify_resource
71             {
72 4     4 0 2404 my $self = shift;
73 4         6 my $identifier = shift;
74 4 100       25 if($identifier =~ m|docker://(.*)$|)
    100          
    100          
75             {
76 1         6 return ('docker', $1);
77             }
78             elsif($identifier =~ m|ssh://(.*)$|)
79             {
80 1         7 return ('ssh', $1);
81             }
82             elsif($identifier =~ m|/|)
83             {
84             # assume it's docker
85 1         5 return ('docker', $identifier);
86             }
87             else
88             {
89             # assume it's ssh
90 1         9 return ('ssh', $identifier);
91             }
92             }
93              
94             sub check_correct_guess
95             {
96 0     0 1 0 my $self = shift;
97 0         0 my $identifier = shift;
98 0         0 my ($type, $server) = $self->identify_resource($identifier);
99 0 0       0 if($type eq 'ssh')
    0          
100             {
101 0         0 return $self->check_ssh_server($server);
102             }
103             elsif($type eq 'docker')
104             {
105 0         0 return $self->check_container($server);
106             }
107             else
108             {
109 0         0 die 'I don\'t know what to do!';
110             }
111             }
112              
113             sub _run_mversion
114             {
115 1     1   2 my $self = shift;
116 1         2 my $cmd = shift;
117             #print "$cmd\n";
118             my ($stdout, $stderr, $exit) = capture {
119 1     1   3033 system(@$cmd);
120 1         27 };
121 1 50       616 if($exit)
122             {
123 1         8 my $command = join(' ', @$cmd);
124 1         29 failure::module::comparison->throw("Failure running $command: $exit");
125             }
126 0         0 my @lines = map { chomp; $_ } grep { !/^\s*$/ } split(/\r\n|\r|\n/, $stdout);
  0         0  
  0         0  
  0         0  
127 0         0 my %versions = map { _module_pair($_) } @lines;
  0         0  
128 0         0 return \%versions;
129             }
130              
131             sub _module_pair
132             {
133 0     0   0 my $line = shift;
134 0         0 my ($module, $version) = $line =~ /(.*) ((:?\d+(:?\.\d+)*|undef))/;
135 0 0       0 if($module)
136             {
137 0         0 return ($module, $version);
138             }
139             else
140             {
141 0         0 my ($missing_module) = $line =~ /'(.*)' does not seem/;
142 0 0       0 unless($missing_module)
143             {
144 0         0 print "Error: $line\n";
145 0         0 return ("error", "error");
146             }
147 0         0 return ($missing_module, 'missing');
148             }
149             }
150              
151             sub _wrap_version
152             {
153 24     24   22 my $version = shift;
154 24 100 66     71 if($version && $version eq 'undef')
155             {
156             # this isn't perfect, in fact it may bite us.
157             # I believe that undef does indicate that a module is
158             # there, it's just it doesn't have $VERSION set.
159 2         4 return Perl::Version->new('0');
160             }
161 22         46 return Perl::Version->new($version);
162             }
163              
164             sub difference_report
165             {
166 2     2 0 2046 my $self = shift;
167 2         3 my $first = shift;
168 2         2 my $second = shift;
169              
170 2         3 my %newer;
171             my %older;
172 0         0 my %newly_installed;
173 0         0 my %removed;
174 2         6 for my $module (keys %$first)
175             {
176 14 100       619 if(exists $second->{$module})
177             {
178 12         17 my $v1 = _wrap_version($first->{$module});
179 12         507 my $v2 = _wrap_version($second->{$module});
180 12 100       410 if($v1 > $v2)
    100          
181             {
182 1         16 $older{$module} = [$first->{$module}, $second->{$module}];
183             }
184             elsif($v1 < $v2)
185             {
186 2         57 $newer{$module} = [$first->{$module}, $second->{$module}];
187             }
188             }
189             else
190             {
191 2         17 $removed{$module} = $first->{$module};
192             }
193             }
194 2         32 for my $module (keys %$second)
195             {
196 13 100       19 unless(exists $first->{$module})
197             {
198 1         2 $newly_installed{$module} = $second->{$module};
199             }
200             }
201             return {
202 2         10 updated => \%newer,
203             downgraded => \%older,
204             removed => \%removed,
205             installed => \%newly_installed,
206             };
207             }
208              
209             sub human_readable_report
210             {
211 3     3 0 3022 my $self = shift;
212 3         3 my $report = shift;
213 3         4 my @lines;
214 3 100       2 if(%{$report->{downgraded}})
  3         10  
215             {
216 2         4 push @lines, 'DOWNGRADED Modules', '';
217 2         1 for my $key (sort keys %{$report->{downgraded}})
  2         7  
218             {
219 2         2 my ($v1, $v2) = @{$report->{downgraded}->{$key}};
  2         5  
220 2         9 push @lines, sprintf("%-40s\t%s -> %s", $key, $v1, $v2);
221             }
222 2         3 push @lines, '';
223             }
224 3 100       3 if(%{$report->{removed}})
  3         6  
225             {
226 2         3 push @lines, 'REMOVED Modules', '';
227 2         3 for my $key (sort keys %{$report->{removed}})
  2         6  
228             {
229 4         2 my $v1 = $report->{removed}->{$key};
230 4         10 push @lines, sprintf("%-40s\t%s", $key, $v1);
231             }
232 2         2 push @lines, '';
233             }
234 3 100       3 if(%{$report->{installed}})
  3         7  
235             {
236 2         2 push @lines, 'Installed Modules', '';
237 2         2 for my $key (sort keys %{$report->{installed}})
  2         3  
238             {
239 2         2 my $v1 = $report->{installed}->{$key};
240 2         5 push @lines, sprintf("%-40s\t%s", $key, $v1);
241             }
242 2         2 push @lines, '';
243             }
244 3 100       2 if(%{$report->{updated}})
  3         8  
245             {
246 2         2 push @lines, 'Updated Modules', '';
247 2         1 for my $key (sort keys %{$report->{updated}})
  2         5  
248             {
249 4         3 my ($v1, $v2) = @{$report->{updated}->{$key}};
  4         5  
250 4         9 push @lines, sprintf("%-40s\t%s -> %s", $key, $v1, $v2);
251             }
252 2         3 push @lines, '';
253             }
254 3 100       6 unless(@lines)
255             {
256 1         3 push @lines, 'No differences', '';
257             }
258 3         603 return join "\n", @lines;
259             }
260              
261             1;
262              
263             __END__