File Coverage

blib/lib/Rex/Repositorio/Repository/Apt.pm
Criterion Covered Total %
statement 30 249 12.0
branch 0 46 0.0
condition 0 24 0.0
subroutine 10 27 37.0
pod n/a
total 40 346 11.5


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::Apt;
8              
9 1     1   1791 use Moose;
  1         2  
  1         6  
10 1     1   4385 use Try::Tiny;
  1         3  
  1         61  
11 1     1   4 use File::Basename qw'basename dirname';
  1         3  
  1         34  
12 1     1   4 use Data::Dumper;
  1         1  
  1         29  
13 1     1   1526 use Digest::SHA;
  1         3242  
  1         82  
14 1     1   7 use Carp;
  1         2  
  1         62  
15 1     1   7 use Params::Validate qw(:all);
  1         2  
  1         222  
16 1     1   11 use File::Spec;
  1         2  
  1         30  
17 1     1   6 use File::Path;
  1         1  
  1         41  
18 1     1   5 use IO::All;
  1         1  
  1         11  
19              
20             our $VERSION = '0.7.0'; # VERSION
21              
22             extends "Rex::Repositorio::Repository::Base";
23              
24             sub mirror {
25 0     0     my ( $self, %option ) = @_;
26              
27 0           $self->repo->{url} =~ s/\/$//;
28 0           $self->repo->{local} =~ s/\/$//;
29 0           my $name = $self->repo->{name};
30              
31 0           my $dist = $self->repo->{dist};
32              
33 0           my @archs = split /, ?/, $self->repo->{arch};
34              
35             ##############################################################################
36             # get meta data
37             ##############################################################################
38 0           my $url = $self->repo->{url} . "/dists/$dist";
39 0           my $contents = $self->download("$url/Release");
40 0           my $ref = $self->_parse_debian_release_file($contents);
41 0           my $arch = $self->repo->{arch};
42              
43 0           my $pr = $self->app->progress_bar(
44             title => "Downloading metadata...",
45             length => 2,
46             );
47              
48             # try download Release and Release.gpg
49             try {
50 0     0     $self->download_metadata(
51             url => $url . "/Release",
52             dest => $self->repo->{local} . "/dists/$dist/Release",
53             force => $option{update_metadata},
54             );
55              
56 0           $pr->update(1);
57              
58 0           $self->download_metadata(
59             url => $url . "/Release.gpg",
60             dest => $self->repo->{local} . "/dists/$dist/Release.gpg",
61             force => $option{update_metadata},
62             );
63              
64 0           $pr->update(2);
65              
66             }
67             catch {
68 0     0     $pr->update(2);
69 0           $self->app->logger->error($_);
70 0           };
71              
72 0           my $i = 0;
73 0           print "\n";
74 0           print "\n";
75 0 0         $pr = $self->app->progress_bar(
76             title => "Downloading file listing...",
77 0           length => scalar( @{ $ref->{SHA1} || $ref->{SHA1Sum} } ),
78             );
79              
80 0 0         for my $file_data ( @{ $ref->{SHA1} || $ref->{SHA1Sum} } ) {
  0            
81 0           $i++;
82 0           $pr->update($i);
83              
84 0           my $file_url = $url . "/" . $file_data->{file};
85 0           my $file = $file_data->{file};
86 0           my $arch_str = join( "|", @archs );
87 0           my $regexp = qr{i18n|((Contents|binary|installer)\-(udeb-)?($arch_str))};
88             next
89 0 0         if ( $file_data->{file} !~ $regexp );
90              
91             try {
92 0     0     $self->download_metadata(
93             url => $file_url,
94             dest => $self->repo->{local} . "/dists/$dist/$file",
95             force => $option{update_metadata},
96             );
97             }
98             catch {
99 0     0     $self->app->logger->info(
100             "Can't find the url: $file_url. " . "This should be no problem." );
101 0           $self->app->logger->info($_);
102 0           };
103             }
104              
105             ##############################################################################
106             # download packages
107             ##############################################################################
108 0 0 0       if ( $option{update_files} || $option{force} ) {
109              
110 0           my @components;
111 0 0         if ( exists $self->repo->{components} ) {
112 0           @components = split /, ?/, $self->repo->{components};
113             }
114             else {
115 0           @components = ( $self->repo->{component} );
116             }
117 0           for my $component (@components) {
118              
119 0           my $local_components_path =
120             $self->app->get_repo_dir( repo => $self->repo->{name} )
121             . "/dists/$dist/$component";
122              
123 0           for my $arch (@archs) {
124 0           $self->app->logger->debug(
125             "Processing ($name, $component) $dist / $arch");
126              
127 0           my $local_packages_path =
128             $local_components_path . "/binary-$arch/Packages.gz";
129              
130 0           $self->app->logger->debug("Reading: $local_packages_path");
131 0           my $content = $self->gunzip( io($local_packages_path)->binary->all );
132 0           my $package_ref = $self->_parse_debian_package_file($content);
133              
134 0           print "\n";
135 0           print "\n";
136 0           $pr = $self->app->progress_bar(
137             title => "Downloading packages for $component ($arch)...",
138 0           length => scalar( @{$package_ref} ),
139             );
140 0           my $pi = 0;
141              
142 0           for my $package ( @{$package_ref} ) {
  0            
143 0           $pi++;
144 0           $pr->update($pi);
145 0           my $package_url = $self->repo->{url} . "/" . $package->{Filename};
146 0           my $package_name = $package->{Package};
147              
148 0           my $local_file = $self->repo->{local} . "/" . $package->{Filename};
149             $self->download_package(
150             url => $package_url,
151             name => $package_name,
152             dest => $local_file,
153             cb => sub {
154 0   0 0     $self->_checksum( @_, "sha1",
155             ( $package->{SHA1} || $package->{SHA1Sum} ) );
156             },
157 0           force => $option{force},
158             update_file => $option{update_files},
159             );
160             }
161             }
162              
163 0 0 0       if ( exists $self->repo->{images} && $self->repo->{images} eq "true" ) {
164              
165             # installer components
166 0           for my $arch (@archs) {
167              
168 0 0         next if ( "\L$arch" eq "all" );
169 0 0         next if ( "\L$component" ne "main" );
170              
171 0           $self->app->logger->debug(
172             "Processing installer ($name, $component) $dist / $arch");
173              
174 0           my $local_packages_path =
175             $local_components_path
176             . "/debian-installer/binary-$arch/Packages.gz";
177              
178 0           $self->app->logger->debug("Reading: $local_packages_path");
179 0           my $content = $self->gunzip( io($local_packages_path)->binary->all );
180 0           my $package_ref = $self->_parse_debian_package_file($content);
181              
182 0           print "\n";
183 0           print "\n";
184 0           $pr = $self->app->progress_bar(
185             title => "Downloading installer packages for $component ($arch)...",
186 0           length => scalar( @{$package_ref} ),
187             );
188 0           my $pi = 0;
189              
190 0           for my $package ( @{$package_ref} ) {
  0            
191 0           $pi++;
192 0           $pr->update($pi);
193 0           my $package_url = $self->repo->{url} . "/" . $package->{Filename};
194 0           my $package_name = $package->{Package};
195              
196 0           my $local_file = $self->repo->{local} . "/" . $package->{Filename};
197             $self->download_package(
198             url => $package_url,
199             name => $package_name,
200             dest => $local_file,
201             cb => sub {
202 0   0 0     $self->_checksum( @_, "sha1",
203             ( $package->{SHA1} || $package->{SHA1Sum} ) );
204             },
205 0           force => $option{update_files}
206             );
207             }
208              
209 0           my $local_file_path =
210             $local_components_path . "/installer-$arch/current/images";
211 0           my $file_ref =
212             $self->_parse_sha256sum_file( $local_file_path . "/SHA256SUMS" );
213              
214 0           print "\n";
215 0           print "\n";
216 0           $pr = $self->app->progress_bar(
217             title =>
218             "Downloading installer image files for $component ($arch)...",
219 0           length => scalar( @{$package_ref} ),
220             );
221 0           my $fi = 0;
222              
223 0           for my $file ( @{$file_ref} ) {
  0            
224 0           $fi++;
225 0           $pr->update($fi);
226 0           my $file_url =
227             $self->repo->{url}
228             . "/dists/$dist/$component/installer-$arch/current/images/"
229             . $file->{file};
230 0           my $file_name = $file->{file};
231              
232 0           my $local_file = File::Spec->catfile( $self->repo->{local},
233             "dists", $dist, $component, "installer-$arch", "current",
234             "images", $file->{file} );
235             $self->download_package(
236             url => $file_url,
237             name => $file_name,
238             dest => $local_file,
239             cb => sub {
240 0     0     $self->_checksum( @_, "sha256", $file->{sha256} );
241             },
242 0           force => $option{update_files}
243             );
244             }
245             }
246             }
247             }
248             }
249             ##############################################################################
250             # download rest of metadata
251             ##############################################################################
252              
253 0           print "\n";
254 0           print "\n";
255 0           $pr = $self->app->progress_bar(
256             title => "Downloading rest of metadata...",
257             length => ( 2 * scalar(@archs) ),
258             );
259              
260 0           my $mi = 0;
261 0           for my $arch (@archs) {
262 0           for my $suffix (qw/bz2 gz/) {
263 0           $mi++;
264 0           $pr->update($mi);
265 0           my $file_url = $url . "/Contents-$arch.$suffix";
266 0           my $file = "Contents-$arch.$suffix";
267              
268             try {
269 0     0     $self->download_metadata(
270             url => $file_url,
271             dest => $self->repo->{local} . "/dists/$dist/$file",
272             force => $option{update_metadata},
273             );
274             }
275             catch {
276 0     0     $self->app->logger->error($_);
277 0           };
278             }
279             }
280              
281             }
282              
283             sub _parse_debian_release_file {
284 0     0     my ( $self, $content ) = @_;
285              
286 0           my $ret = {};
287 0           my $section = "main";
288 0           for my $line ( split /\n/, $content ) {
289 0           chomp $line;
290 0 0         next if ( $line =~ m/^\s*?$/ );
291              
292 0 0         if ( $line !~ m/^\s/ ) {
293 0           my ( $key, $value ) = split /:/, $line;
294 0           $value =~ s/^\s*|\s*$//;
295 0           $section = $key;
296 0 0         if ($value) {
297 0           $ret->{$key} = $value;
298             }
299             else {
300 0           $ret->{$key} = [];
301             }
302             }
303              
304 0 0         if ( $line =~ m/^\s/ ) {
305 0           $line =~ s/^\s//;
306 0           my @values = split /\s+/, $line;
307 0 0 0       if ( $ret->{$section} && !ref $ret->{$section} ) {
308 0           $ret->{$section} = [ $ret->{$section} ];
309             }
310 0           push @{ $ret->{$section} },
  0            
311             {
312             checksum => $values[0],
313             size => $values[1],
314             file => $values[2],
315             };
316             }
317             }
318              
319 0           return $ret;
320             }
321              
322             sub _parse_debian_package_file {
323 0     0     my ( $self, $content ) = @_;
324              
325 0           my @ret;
326              
327             my $section;
328 0           my $current_section;
329 0           for my $line ( split /\n/, $content ) {
330 0           chomp $line;
331              
332 0 0         if ( $line =~ m/^$/ ) {
333 0           push @ret, $current_section;
334 0           $current_section = {};
335 0           next;
336             }
337              
338 0           my ( $key, $value ) = ( $line =~ m/^([A-Z0-9a-z\-]+):(.*)$/ );
339              
340 0 0         if ($key) {
341 0           $value =~ s/^\s//;
342 0           $section = $key;
343 0           $current_section->{$key} = $value;
344             }
345             else {
346 0           $value = $line;
347 0           $value =~ s/^\s//;
348              
349 0 0 0       if ( $current_section->{$section} && !ref $current_section->{$section} ) {
350 0           $current_section->{$section} = [ $current_section->{$section} ];
351             }
352              
353 0           push @{ $current_section->{$section} }, $value;
  0            
354             }
355             }
356              
357 0           return \@ret;
358             }
359              
360             sub _parse_sha256sum_file {
361 0     0     my ( $self, $file ) = @_;
362 0           my @files;
363 0 0         open my $fh, "<", $file or die $!;
364 0           while ( my $line = <$fh> ) {
365 0           my ( $sum, $file_name ) = split( /\s+/, $line );
366 0           $file_name =~ s/^\.\///;
367 0           push @files, { sha256 => $sum, file => $file_name };
368             }
369 0           close $fh;
370              
371 0           return \@files;
372             }
373              
374             sub init {
375 0     0     my $self = shift;
376              
377 0           my $dist = $self->repo->{dist};
378 0           my $arch = $self->repo->{arch};
379 0           my $component = $self->repo->{component};
380 0   0       my $desc = $self->repo->{description} || "$component repository";
381              
382 0           my $repo_dir = $self->app->get_repo_dir( repo => $self->repo->{name} );
383 0           mkpath "$repo_dir/dists/$dist/$component/binary-$arch";
384              
385 0           my $pool_dir = $self->app->get_repo_dir( repo => $self->repo->{name} ) . "/"
386             . "pool/$dist/$component/";
387              
388 0           mkpath $pool_dir;
389              
390 0           my $aptftp = io("$repo_dir/aptftp.conf");
391 0           my $aptgenerate = io("$repo_dir/aptgenerate.conf");
392              
393 0           $aptftp->print(<<" EOF");
394             APT::FTPArchive::Release {
395             Origin "$component";
396             Label "$component";
397             Suite "$dist";
398             Codename "$dist";
399             Architectures "$arch";
400             Components "$component";
401             Description "$desc";
402             };
403              
404             EOF
405              
406 0           $aptgenerate->print(<<" EOF");
407             Dir::ArchiveDir ".";
408             Dir::CacheDir ".";
409             TreeDefault::Directory "pool/$dist/";
410             TreeDefault::SrcDirectory "pool/$dist/";
411             Default::Packages::Extensions ".deb";
412             Default::Packages::Compress ". gzip bzip2";
413             Default::Sources::Compress "gzip bzip2";
414             Default::Contents::Compress "gzip bzip2";
415              
416             BinDirectory "dists/$dist/$component/binary-$arch" {
417             Packages "dists/$dist/$component/binary-$arch/Packages";
418             Contents "dists/$dist/Contents-$arch";
419             };
420              
421             Tree "dists/$dist" {
422             Sections "$component";
423             Architectures "$arch";
424             };
425             EOF
426              
427 0           $self->_run_ftp_archive();
428             }
429              
430             sub add_file {
431 0     0     my $self = shift;
432 0           my %option = validate(
433             @_,
434             {
435             file => {
436             type => SCALAR
437             },
438             }
439             );
440              
441 0           my $dist = $self->repo->{dist};
442 0           my $component = $self->repo->{component};
443              
444 0           my $dest =
445             $self->app->get_repo_dir( repo => $self->repo->{name} ) . "/"
446             . "pool/$dist/$component/"
447             . basename( $option{file} );
448              
449 0           $self->add_file_to_repo( source => $option{file}, dest => $dest );
450              
451 0           $self->_run_ftp_archive();
452             }
453              
454             sub remove_file {
455 0     0     my $self = shift;
456              
457 0           my %option = validate(
458             @_,
459             {
460             file => {
461             type => SCALAR
462             },
463             }
464             );
465              
466 0           my $dist = $self->repo->{dist};
467 0           my $component = $self->repo->{component};
468              
469 0           my $file =
470             $self->app->get_repo_dir( repo => $self->repo->{name} ) . "/"
471             . "pool/$dist/$component/"
472             . basename( $option{file} );
473              
474 0           $self->remove_file_from_repo( file => $file );
475              
476 0           $self->_run_ftp_archive();
477             }
478              
479             sub _run_ftp_archive {
480 0     0     my $self = shift;
481              
482 0           my $dist = $self->repo->{dist};
483 0           my $repo_dir = $self->app->get_repo_dir( repo => $self->repo->{name} );
484              
485 0           system
486             "cd $repo_dir ; apt-ftparchive generate -c=aptftp.conf aptgenerate.conf";
487              
488 0 0         if ( $? != 0 ) {
489 0           confess "Error running apt-ftparchive generate";
490             }
491              
492             system
493 0           "cd $repo_dir ; apt-ftparchive release -c=aptftp.conf dists/$dist >dists/$dist/Release";
494              
495 0 0         if ( $? != 0 ) {
496 0           confess "Error running apt-ftparchive release";
497             }
498              
499 0 0 0       if ( exists $self->repo->{gpg} && $self->repo->{gpg}->{key} ) {
500 0           my $key = $self->repo->{gpg}->{key};
501 0           my $pass = $self->repo->{gpg}->{password};
502 0 0         if ( !$pass ) {
503 0           $pass = $self->read_password("GPG key passphrase: ");
504             }
505              
506 0           unlink "$repo_dir/dists/$dist/Release.gpg";
507              
508 0           my $cmd =
509             "cd $repo_dir ; gpg -u $key "
510             . "--batch --passphrase '"
511             . $pass
512             . "' -bao dists/$dist/Release.gpg dists/$dist/Release";
513              
514 0           system $cmd;
515              
516 0 0         if ( $? != 0 ) {
517 0           $cmd =~ s/\Q$pass\E/\*\*\*\*\*\*\*/;
518 0           confess "Error running gpg sign: $cmd";
519             }
520              
521             # export pub key as asc file
522 0           my $pub_file = $self->repo->{name} . ".asc";
523 0           $cmd = "cd $repo_dir ; gpg -a --output $pub_file --export $key";
524 0           system $cmd;
525              
526 0 0         if ( $? != 0 ) {
527 0           confess "Error running gpg export: $cmd";
528             }
529             }
530             }
531              
532             # test if all necessary parameters are available
533             override verify_options => sub {
534             my $self = shift;
535             super();
536              
537             if ( !exists $self->repo->{local} ) {
538             confess "No local path (local) given for: " . $self->repo->{name};
539             }
540              
541             if ( !exists $self->repo->{arch} ) {
542             confess "No architecture (arch) given for: " . $self->repo->{name};
543             }
544              
545             if ( !exists $self->repo->{dist} ) {
546             confess "No distribution (dist) given for: " . $self->repo->{name};
547             }
548              
549             if ( !exists $self->repo->{component} ) {
550             confess "No component (component) given for: " . $self->repo->{name};
551             }
552             };
553              
554             1;