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   1003 use Moose;
  1         2  
  1         11  
10 1     1   7087 use Try::Tiny;
  1         3  
  1         96  
11 1     1   8 use common::sense;
  1         2  
  1         11  
12 1     1   72 use Carp;
  1         2  
  1         78  
13 1     1   7 use English;
  1         2  
  1         8  
14 1     1   575 use LWP::UserAgent;
  1         2  
  1         27  
15 1     1   436 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 = '0.6.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             confess "Error downloading $url.";
88             }
89              
90             return $resp->content;
91             }
92              
93             sub get_xml {
94             my ( $self, $xml ) = @_;
95             return XML::LibXML->load_xml( string => $xml );
96             }
97              
98             sub decode_xml {
99             my ( $self, $xml ) = @_;
100             return XMLin( $xml, ForceArray => 1 );
101             }
102              
103             sub download_package {
104             my $self = shift;
105             my %option = validate(
106             @_,
107             {
108             name => {
109             type => SCALAR
110             },
111             url => {
112             type => SCALAR
113             },
114             dest => {
115             type => SCALAR
116             },
117             cb => {
118             type => CODEREF,
119             optional => 1,
120             },
121             force => {
122             type => BOOLEAN,
123             optional => 1,
124             }
125             }
126             );
127              
128             my $package_file =
129             $self->app->config->{RepositoryRoot} . "/head/" . $option{dest};
130             $self->_download_binary_file(
131             dest => $package_file,
132             url => $option{url},
133             cb => $option{cb},
134             force => $option{force},
135             );
136             }
137              
138             sub download_metadata {
139             my $self = shift;
140             my %option = validate(
141             @_,
142             {
143             url => {
144             type => SCALAR
145             },
146             dest => {
147             type => SCALAR
148             },
149             cb => {
150             type => CODEREF,
151             optional => 1,
152             },
153             force => {
154             type => BOOLEAN,
155             optional => 1,
156             }
157             }
158             );
159              
160             my $metadata_file =
161             $self->app->config->{RepositoryRoot} . "/head/" . $option{dest};
162             $self->_download_binary_file(
163             dest => $metadata_file,
164             url => $option{url},
165             cb => $option{cb},
166             force => $option{force},
167             );
168             }
169              
170             sub _download_binary_file {
171             my $self = shift;
172             my %option = validate(
173             @_,
174             {
175             url => {
176             type => SCALAR
177             },
178             dest => {
179             type => SCALAR
180             },
181             cb => {
182             type => CODEREF | UNDEF,
183             optional => 1,
184             },
185             force => {
186             type => BOOLEAN
187             }
188             }
189             );
190              
191             $self->app->logger->debug("Downloading: $option{url} -> $option{dest}");
192              
193             mkpath( dirname( $option{dest} ) ) if ( !-d dirname $option{dest} );
194              
195             if ( -f $option{dest} && !$option{force} ) {
196             $self->app->logger->debug("Skipping $option{url}. File already exists.");
197             return;
198             }
199              
200             if ( !-w dirname( $option{dest} ) ) {
201             $self->app->logger->error( "Can't write to " . dirname( $option{dest} ) );
202             confess "Can't write to " . dirname( $option{dest} );
203             }
204              
205             if ( -f $option{dest} && $option{force} ) {
206             unlink $option{dest};
207             }
208              
209             open my $fh, ">", $option{dest};
210             binmode $fh;
211             my $resp = $self->ua->get(
212             $option{url},
213             ':content_cb' => sub {
214             my ( $data, $response, $protocol ) = @_;
215             print $fh $data;
216             }
217             );
218             close $fh;
219              
220             if ( !$resp->is_success ) {
221             $self->app->logger->error("Can't download $option{url}.");
222             $self->app->logger->error( "Status: " . $resp->status_line );
223             confess "Error downloading $option{url}.";
224             }
225              
226             $option{cb}->( $option{dest} ) if ( exists $option{cb} && $option{cb} );
227             }
228              
229             sub add_file_to_repo {
230             my $self = shift;
231             my %option = validate(
232             @_,
233             {
234             source => {
235             type => SCALAR
236             },
237             dest => {
238             type => SCALAR
239             }
240             }
241             );
242              
243             if ( !-f $option{source} ) {
244             $self->app->logger->error("Fild $option{source} not found.");
245             confess "Fild $option{source} not found.";
246             }
247              
248             $self->app->logger->debug("Copy $option{source} -> $option{dest}");
249             my $ret = copy $option{source}, $option{dest};
250             if ( !$ret ) {
251             $self->app->logger->error(
252             "Error copying file $option{source} to $option{dest}");
253             confess "Error copying file $option{source} to $option{dest}";
254             }
255             }
256              
257             sub remove_file_from_repo {
258             my $self = shift;
259             my %option = validate(
260             @_,
261             {
262             file => {
263             type => SCALAR
264             }
265             }
266             );
267              
268             if ( !-f $option{file} ) {
269             $self->app->logger->error("Fild $option{file} not found.");
270             confess "Fild $option{file} not found.";
271             }
272              
273             $self->app->logger->debug("Deleting $option{file}.");
274             my $ret = unlink $option{file};
275             if ( !$ret ) {
276             $self->app->logger->error("Error deleting file $option{file}");
277             confess "Error deleting file $option{file}";
278             }
279             }
280              
281             sub _checksum_md5 {
282             my ( $self, $file, $wanted_checksum ) = @_;
283             my $md5 = Digest::MD5->new;
284             open my $fh, "<", $file;
285             binmode $fh;
286             $md5->addfile($fh);
287              
288             my $file_checksum = $md5->hexdigest;
289              
290             close $fh;
291              
292             $self->app->logger->debug(
293             "wanted_checksum: $wanted_checksum == $file_checksum");
294              
295             if ( $wanted_checksum ne $file_checksum ) {
296             $self->app->logger->error("Checksum for $file wrong.");
297             confess "Checksum of $file wrong.";
298             }
299             }
300              
301             sub _checksum {
302             my ( $self, $file, $type, $wanted_checksum ) = @_;
303              
304             my $c_type = 1;
305             if ( $type eq "sha256" ) {
306             $c_type = "256";
307             }
308             elsif ( $type eq "md5" ) {
309             return $self->_checksum_md5( $file, $wanted_checksum );
310             }
311              
312             my $sha = Digest::SHA->new($c_type);
313             $sha->addfile($file);
314             my $file_checksum = $sha->hexdigest;
315              
316             $self->app->logger->debug(
317             "wanted_checksum: $wanted_checksum == $file_checksum");
318              
319             if ( $wanted_checksum ne $file_checksum ) {
320             $self->app->logger->error("Checksum for $file wrong.");
321             confess "Checksum of $file wrong.";
322             }
323             }
324              
325             sub verify_options {
326             my ($self) = @_;
327              
328             if ( !exists $self->app->config->{RepositoryRoot}
329             || !$self->app->config->{RepositoryRoot} )
330             {
331             confess "No repository root (RepositoryRoot) given in configuration file.";
332             }
333             }
334              
335             sub read_password {
336             my ( $self, $msg ) = @_;
337             $msg ||= "Password: ";
338              
339             print $msg;
340             ReadMode "noecho";
341             my $password = <STDIN>;
342             chomp $password;
343             ReadMode 0;
344             print "\n";
345             return $password;
346             }
347              
348             sub get_errata {
349             my $self = shift;
350             my %option = validate(
351             @_,
352             {
353             package => {
354             type => SCALAR
355             },
356             version => {
357             type => SCALAR
358             },
359             arch => {
360             type => SCALAR
361             },
362             }
363             );
364              
365             my $errata_dir =
366             $self->app->get_errata_dir( repo => $self->repo->{name}, tag => "head" );
367              
368             if (
369             !-f File::Spec->catfile(
370             $errata_dir, $option{arch},
371             substr( $option{package}, 0, 1 ), $option{package},
372             "errata.json"
373             )
374             )
375             {
376             return {};
377             }
378              
379             my $ref = decode_json(
380             IO::All->new(
381             File::Spec->catfile(
382             $errata_dir, $option{arch},
383             substr( $option{package}, 0, 1 ), $option{package},
384             "errata.json"
385             )
386             )->slurp
387             );
388              
389             my $package = $option{package};
390             my $arch = $option{arch};
391             my $version = $option{version};
392              
393             my $pkg = $ref;
394             my @versions = keys %{$pkg};
395              
396             @versions = sort { $a cmp $b } @versions;
397              
398             my $idx = firstidx { ( $_ cmp $version ) == 1 } @versions;
399             if ( $idx == -1 ) {
400              
401             # no updates found
402             return {};
403             }
404              
405             $idx = 0 if ( $idx <= 0 );
406              
407             my @update_versions = @versions[ $idx .. $#versions ];
408             my $ret;
409             for my $uv (@update_versions) {
410             $ret->{$uv} = $pkg->{$uv};
411             }
412              
413             return $ret;
414             }
415              
416             sub update_errata {
417             my $self = shift;
418              
419             my $errata_type = $self->repo->{errata};
420             $self->app->logger->debug("Updating errata of type: $errata_type");
421              
422             my $data = $self->download("http://errata.repositor.io/$errata_type.tar.gz");
423             open( my $fh, ">", "/tmp/$errata_type.tar.gz" ) or confess($!);
424             binmode $fh;
425             print $fh $data;
426             close($fh);
427              
428             my $errata_dir =
429             $self->app->get_errata_dir( repo => $self->repo->{name}, tag => "head" );
430              
431             mkpath $errata_dir;
432              
433             system "cd $errata_dir ; tar xzf /tmp/$errata_type.tar.gz";
434              
435             if ( $? != 0 ) {
436             confess "Error extracting errata database.";
437             }
438              
439             unlink "/tmp/$errata_type.tar.gz";
440              
441             $self->app->logger->debug("Updating errata of type: $errata_type (done)");
442             }
443              
444             1;