File Coverage

blib/lib/Rex/Repositorio/Repository/Apt.pm
Criterion Covered Total %
statement 30 248 12.1
branch 0 44 0.0
condition 0 21 0.0
subroutine 10 27 37.0
pod n/a
total 40 340 11.7


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