File Coverage

blib/lib/Rex/Repositorio/Repository/Base.pm
Criterion Covered Total %
statement 19 21 90.4
branch n/a
condition n/a
subroutine 7 7 100.0
pod n/a
total 26 28 92.8


line stmt bran cond sub pod time code
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             package Rex::Repositorio::Repository::Base;
8              
9 1     1   616 use Moose;
  1         1  
  1         8  
10 1     1   4881 use Try::Tiny;
  1         2  
  1         44  
11 1     1   4 use common::sense;
  1         1  
  1         6  
12 1     1   35 use Carp;
  1         1  
  1         39  
13 1     1   3 use English;
  1         1  
  1         5  
14 1     1   278 use LWP::UserAgent;
  1         1  
  1         15  
15 1     1   233 use XML::LibXML;
  0            
  0            
16             use XML::Simple;
17             use Params::Validate qw(:all);
18             use IO::All;
19             use File::Path;
20             use File::Basename qw'dirname';
21             use File::Spec;
22             use File::Copy;
23             use Digest::SHA;
24             use Digest::MD5;
25             use Term::ReadKey;
26             use JSON::XS;
27             use List::MoreUtils 'firstidx';
28              
29             our $VERSION = '1.0.0'; # VERSION
30              
31             has app => ( is => 'ro' );
32             has repo => ( is => 'ro' );
33              
34             sub ua {
35             my ($self) = @_;
36              
37             my %option;
38             if ( exists $self->repo->{key} && exists $self->repo->{cert} ) {
39              
40             # we need ssl client cert authentication
41             $option{ssl_opts} = {
42             SSL_cert_file => $self->repo->{cert},
43             SSL_key_file => $self->repo->{key},
44             SSL_ca_file => $self->repo->{ca},
45             };
46             }
47              
48             return $self->app->ua(%option);
49             }
50              
51             sub download_gzip {
52             my ( $self, $url ) = @_;
53              
54             my $content = $self->download($url);
55              
56             require Compress::Zlib;
57              
58             $self->app->logger->debug("Starting uncompressing of: $url");
59              
60             my $un_content = Compress::Zlib::memGunzip($content);
61             $self->app->logger->debug("Finished uncompressing of: $url");
62             if ( !$un_content ) {
63             $self->app->logger->error("Error uncompressing data.");
64             confess "Error uncompressing data.";
65             }
66              
67             return $un_content;
68             }
69              
70             sub gunzip {
71             my ( $self, $data ) = @_;
72             require Compress::Zlib;
73              
74             return Compress::Zlib::memGunzip($data);
75             }
76              
77             sub download {
78             my ( $self, $url ) = @_;
79              
80             $self->app->logger->debug("Starting download of: $url");
81             my $resp = $self->ua->get($url);
82             $self->app->logger->debug("Finished download of: $url");
83              
84             if ( !$resp->is_success ) {
85             $self->app->logger->error("Can't download $url.");
86             $self->app->logger->error( "Status: " . $resp->status_line );
87             my $retry_count = 1;
88             while ( !$resp->is_success
89             && $retry_count <= ( $self->app->config->{DownloadRetryCount} // 3 ) )
90             {
91             $self->app->logger->error("Retry downloading of url: $url");
92             $retry_count += 1;
93             $resp = $self->ua->get($url);
94             }
95             if ( !$resp->is_success ) {
96             confess "Error downloading $url.";
97             }
98             }
99              
100             return $resp->content;
101             }
102              
103             sub get_xml {
104             my ( $self, $xml ) = @_;
105             return XML::LibXML->load_xml( string => $xml );
106             }
107              
108             sub decode_xml {
109             my ( $self, $xml ) = @_;
110             return XMLin( $xml, ForceArray => 1 );
111             }
112              
113             sub download_package {
114             my $self = shift;
115             my %option = validate(
116             @_,
117             {
118             name => {
119             type => SCALAR
120             },
121             url => {
122             type => SCALAR
123             },
124             dest => {
125             type => SCALAR
126             },
127             cb => {
128             type => CODEREF,
129             optional => 1,
130             },
131             update_file => {
132             type => BOOLEAN,
133             optional => 1,
134             },
135             force => {
136             type => BOOLEAN,
137             optional => 1,
138             },
139             }
140             );
141              
142             my $package_file =
143             $self->app->config->{RepositoryRoot} . "/head/" . $option{dest};
144             $self->_download_binary_file(
145             dest => $package_file,
146             url => $option{url},
147             cb => $option{cb},
148             force => $option{force},
149             update_file => $option{update_file},
150             );
151             }
152              
153             sub download_metadata {
154             my $self = shift;
155             my %option = validate(
156             @_,
157             {
158             url => {
159             type => SCALAR
160             },
161             dest => {
162             type => SCALAR
163             },
164             cb => {
165             type => CODEREF,
166             optional => 1,
167             },
168             force => {
169             type => BOOLEAN,
170             optional => 1,
171             }
172             }
173             );
174              
175             my $metadata_file =
176             $self->app->config->{RepositoryRoot} . "/head/" . $option{dest};
177             $self->_download_binary_file(
178             dest => $metadata_file,
179             url => $option{url},
180             cb => $option{cb},
181             force => $option{force},
182             );
183             }
184              
185             sub _download_binary_file {
186             my $self = shift;
187             my %option = validate(
188             @_,
189             {
190             url => {
191             type => SCALAR
192             },
193             dest => {
194             type => SCALAR
195             },
196             cb => {
197             type => CODEREF | UNDEF,
198             optional => 1,
199             },
200             force => {
201             type => BOOLEAN,
202             optional => 1,
203             },
204             update_file => {
205             type => BOOLEAN,
206             optional => 1,
207             },
208             }
209             );
210              
211             $self->app->logger->debug("Downloading: $option{url} -> $option{dest}");
212              
213             mkpath( dirname( $option{dest} ) ) if ( !-d dirname $option{dest} );
214              
215             if ( exists $option{cb}
216             && ref $option{cb} eq "CODE"
217             && $option{update_file}
218             && -f $option{dest} )
219             {
220             eval {
221             $option{cb}->( $option{dest} );
222             1;
223             } or do {
224              
225             # if callback is failing, we need to download the file once again.
226             # so just set force to true
227             $self->app->logger->debug(
228             "Setting option force -> 1: update_file is enabled and callback failed."
229             );
230             $option{force} = 1;
231             };
232             }
233              
234             if ( -f $option{dest} && !$option{force} ) {
235             $self->app->logger->debug("Skipping $option{url}. File already exists.");
236             return;
237             }
238              
239             if ( !-w dirname( $option{dest} ) ) {
240             $self->app->logger->error( "Can't write to " . dirname( $option{dest} ) );
241             confess "Can't write to " . dirname( $option{dest} );
242             }
243              
244             if ( -f $option{dest} && $option{force} ) {
245             unlink $option{dest};
246             }
247              
248             open my $fh, ">", $option{dest};
249             binmode $fh;
250             my $resp = $self->ua->get(
251             $option{url},
252             ':content_cb' => sub {
253             my ( $data, $response, $protocol ) = @_;
254             print $fh $data;
255             }
256             );
257             close $fh;
258              
259             if ( !$resp->is_success ) {
260             $self->app->logger->error("Can't download $option{url}.");
261             $self->app->logger->error( "Status: " . $resp->status_line );
262             confess "Error downloading $option{url}.";
263             }
264              
265             $option{cb}->( $option{dest} )
266             if ( exists $option{cb} && ref $option{cb} eq "CODE" );
267             }
268              
269             sub add_file_to_repo {
270             my $self = shift;
271             my %option = validate(
272             @_,
273             {
274             source => {
275             type => SCALAR
276             },
277             dest => {
278             type => SCALAR
279             }
280             }
281             );
282              
283             if ( !-f $option{source} ) {
284             $self->app->logger->error("Fild $option{source} not found.");
285             confess "Fild $option{source} not found.";
286             }
287              
288             $self->app->logger->debug("Copy $option{source} -> $option{dest}");
289             my $ret = copy $option{source}, $option{dest};
290             if ( !$ret ) {
291             $self->app->logger->error(
292             "Error copying file $option{source} to $option{dest}");
293             confess "Error copying file $option{source} to $option{dest}";
294             }
295             }
296              
297             sub remove_file_from_repo {
298             my $self = shift;
299             my %option = validate(
300             @_,
301             {
302             file => {
303             type => SCALAR
304             }
305             }
306             );
307              
308             if ( !-f $option{file} ) {
309             $self->app->logger->error("Fild $option{file} not found.");
310             confess "Fild $option{file} not found.";
311             }
312              
313             $self->app->logger->debug("Deleting $option{file}.");
314             my $ret = unlink $option{file};
315             if ( !$ret ) {
316             $self->app->logger->error("Error deleting file $option{file}");
317             confess "Error deleting file $option{file}";
318             }
319             }
320              
321             sub _checksum_md5 {
322             my ( $self, $file, $wanted_checksum ) = @_;
323             my $md5 = Digest::MD5->new;
324             open my $fh, "<", $file;
325             binmode $fh;
326             $md5->addfile($fh);
327              
328             my $file_checksum = $md5->hexdigest;
329              
330             close $fh;
331              
332             $self->app->logger->debug(
333             "wanted_checksum: $wanted_checksum == $file_checksum");
334              
335             if ( $wanted_checksum ne $file_checksum ) {
336             $self->app->logger->error("Checksum for $file wrong.");
337             confess "Checksum of $file wrong.";
338             }
339             }
340              
341             sub _checksum {
342             my ( $self, $file, $type, $wanted_checksum ) = @_;
343              
344             my $c_type = 1;
345             if ( $type eq "sha256" ) {
346             $c_type = "256";
347             }
348             elsif ( $type eq "md5" ) {
349             return $self->_checksum_md5( $file, $wanted_checksum );
350             }
351              
352             my $sha = Digest::SHA->new($c_type);
353             $sha->addfile($file);
354             my $file_checksum = $sha->hexdigest;
355              
356             $self->app->logger->debug(
357             "wanted_checksum: $wanted_checksum == $file_checksum");
358              
359             if ( $wanted_checksum ne $file_checksum ) {
360             $self->app->logger->error("Checksum for $file wrong.");
361             confess "Checksum of $file wrong.";
362             }
363             }
364              
365             sub verify_options {
366             my ($self) = @_;
367              
368             if ( !exists $self->app->config->{RepositoryRoot}
369             || !$self->app->config->{RepositoryRoot} )
370             {
371             confess "No repository root (RepositoryRoot) given in configuration file.";
372             }
373             }
374              
375             sub read_password {
376             my ( $self, $msg ) = @_;
377             $msg ||= "Password: ";
378              
379             print $msg;
380             ReadMode "noecho";
381             my $password = <STDIN>;
382             chomp $password;
383             ReadMode 0;
384             print "\n";
385             return $password;
386             }
387              
388             sub get_errata {
389             my $self = shift;
390             my %option = validate(
391             @_,
392             {
393             package => {
394             type => SCALAR
395             },
396             version => {
397             type => SCALAR
398             },
399             arch => {
400             type => SCALAR
401             },
402             }
403             );
404              
405             my $errata_dir =
406             $self->app->get_errata_dir( repo => $self->repo->{name}, tag => "head" );
407              
408             if (
409             !-f File::Spec->catfile(
410             $errata_dir, $option{arch},
411             substr( $option{package}, 0, 1 ), $option{package},
412             "errata.json"
413             )
414             )
415             {
416             return {};
417             }
418              
419             my $ref = decode_json(
420             IO::All->new(
421             File::Spec->catfile(
422             $errata_dir, $option{arch},
423             substr( $option{package}, 0, 1 ), $option{package},
424             "errata.json"
425             )
426             )->slurp
427             );
428              
429             my $package = $option{package};
430             my $arch = $option{arch};
431             my $version = $option{version};
432              
433             my $pkg = $ref;
434             my @versions = keys %{$pkg};
435              
436             @versions = sort { $a cmp $b } @versions;
437              
438             my $idx = firstidx { ( $_ cmp $version ) == 1 } @versions;
439             if ( $idx == -1 ) {
440              
441             # no updates found
442             return {};
443             }
444              
445             $idx = 0 if ( $idx <= 0 );
446              
447             my @update_versions = @versions[ $idx .. $#versions ];
448             my $ret;
449             for my $uv (@update_versions) {
450             $ret->{$uv} = $pkg->{$uv};
451             }
452              
453             return $ret;
454             }
455              
456             sub update_errata {
457             my $self = shift;
458              
459             my $errata_type = $self->repo->{errata};
460             $self->app->logger->debug("Updating errata of type: $errata_type");
461              
462             my $data = $self->download("http://errata.repositor.io/$errata_type.tar.gz");
463             open( my $fh, ">", "/tmp/$errata_type.tar.gz" ) or confess($!);
464             binmode $fh;
465             print $fh $data;
466             close($fh);
467              
468             my $errata_dir =
469             $self->app->get_errata_dir( repo => $self->repo->{name}, tag => "head" );
470              
471             mkpath $errata_dir;
472              
473             system "cd $errata_dir ; tar xzf /tmp/$errata_type.tar.gz";
474              
475             if ( $? != 0 ) {
476             confess "Error extracting errata database.";
477             }
478              
479             unlink "/tmp/$errata_type.tar.gz";
480              
481             $self->app->logger->debug("Updating errata of type: $errata_type (done)");
482             }
483              
484             1;