File Coverage

blib/lib/Rex/Repositorio.pm
Criterion Covered Total %
statement 16 18 88.8
branch n/a
condition n/a
subroutine 6 6 100.0
pod n/a
total 22 24 91.6


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;
8              
9 1     1   1187 use Moose;
  1         367590  
  1         10  
10 1     1   8713 use English;
  1         2250  
  1         8  
11 1     1   1054 use common::sense;
  1         8  
  1         6  
12 1     1   51 use Carp;
  1         1  
  1         55  
13 1     1   565 use LWP::UserAgent;
  1         31970  
  1         35  
14 1     1   240 use XML::LibXML;
  0            
  0            
15             use XML::Simple;
16             use Params::Validate qw(:all);
17             use IO::All;
18             use File::Path;
19             use File::Basename qw'dirname';
20             use File::Spec;
21             use File::Copy;
22             use Rex::Repositorio::Repository_Factory;
23             use JSON::XS;
24             use Data::Dumper;
25             use Term::ProgressBar;
26             use Term::ANSIColor;
27              
28             our $VERSION = '0.6.0'; # VERSION
29              
30             has config => ( is => 'ro' );
31             has logger => ( is => 'ro' );
32              
33             sub ua {
34             my ( $self, %option ) = @_;
35             my $ua = LWP::UserAgent->new;
36             $ua->env_proxy;
37              
38             if ( exists $option{ssl_opts} ) {
39             for my $key ( keys %{ $option{ssl_opts} } ) {
40             $ua->ssl_opts( $key, $option{ssl_opts}->{$key} );
41             }
42             }
43              
44             return $ua;
45             }
46              
47             sub run {
48             my ( $self, %option ) = @_;
49              
50             $self->config->{RepositoryRoot} =~ s/\/$//;
51             $self->parse_cli_option(%option);
52             }
53              
54             sub parse_cli_option {
55             my ( $self, %option ) = @_;
56              
57             if ( exists $option{help} ) {
58             $self->_help();
59             exit 0;
60             }
61              
62             if ( exists $option{mirror} && exists $option{repo} ) {
63             $self->print_info("Going to mirror " . $option{repo} . ". This may take a while.");
64             print "\n";
65              
66             $self->mirror(
67             repo => $option{repo},
68             update_metadata => $option{"update-metadata"},
69             update_files => $option{"update-files"},
70             );
71              
72             print "\n"; print "\n";
73             $self->print_info("Finished downloading of files for " . $option{repo});
74             print "\n";
75             }
76              
77             elsif ( exists $option{tag} && exists $option{repo} ) {
78             $self->tag( tag => $option{tag}, repo => $option{repo} );
79             }
80              
81             elsif ( exists $option{repo} && exists $option{"update-errata"} ) {
82             $self->update_errata( repo => $option{repo} );
83             }
84              
85             elsif ( exists $option{errata}
86             && exists $option{package}
87             && exists $option{arch}
88             && exists $option{repo}
89             && exists $option{version} )
90             {
91             $self->print_errata(
92             package => $option{package},
93             arch => $option{arch},
94             version => $option{version},
95             repo => $option{repo},
96             );
97             }
98              
99             elsif ( exists $option{server} && exists $option{repo} ) {
100             $self->server( repo => $option{repo} );
101             }
102              
103             elsif ( exists $option{list} ) {
104             $self->list();
105             }
106              
107             elsif ( exists $option{init} && exists $option{repo} ) {
108             $self->init( repo => $option{repo} );
109             }
110              
111             elsif ( exists $option{"add-file"} && exists $option{repo} ) {
112             $self->add_file( file => $option{"add-file"}, repo => $option{repo} );
113             }
114              
115             elsif ( exists $option{"remove-file"} && exists $option{repo} ) {
116             $self->remove_file( file => $option{"remove-file"}, repo => $option{repo} );
117             }
118              
119             else {
120             $self->_help();
121             exit 0;
122             }
123             }
124              
125             sub server {
126             my $self = shift;
127             my %option = validate(
128             @_,
129             {
130             repo => {
131             type => SCALAR
132             },
133             }
134             );
135              
136             require Mojolicious::Commands;
137              
138             # pass config to mojo app
139             $ENV{'REPO_CONFIG'} = encode_json( $self->config );
140             $ENV{'REPO_NAME'} = $option{repo};
141             $ENV{'MOJO_MAX_MESSAGE_SIZE'} = 1024 * 1024 * 1024 * 1024; # set max_message_size astronomically high / TODO: make it configurable
142             my $server_type = $self->config->{Repository}->{ $option{repo} }->{type};
143             if ( $server_type eq "Apt" ) {
144             $server_type = "Yum";
145             }
146             Mojolicious::Commands->start_app("Rex::Repositorio::Server::$server_type");
147             }
148              
149             sub add_file {
150             my $self = shift;
151             my %option = validate(
152             @_,
153             {
154             file => {
155             type => SCALAR
156             },
157             repo => {
158             type => SCALAR
159             }
160             }
161             );
162              
163             my $repo = $self->config->{Repository}->{ $option{repo} };
164             my $type = $repo->{type};
165             my $repo_o = Rex::Repositorio::Repository_Factory->create(
166             type => $type,
167             options => {
168             app => $self,
169             repo => {
170             name => $option{repo},
171             %{$repo},
172             }
173             }
174             );
175              
176             $repo_o->add_file( file => $option{file} );
177             }
178              
179             sub remove_file {
180             my $self = shift;
181             my %option = validate(
182             @_,
183             {
184             file => {
185             type => SCALAR
186             },
187             repo => {
188             type => SCALAR
189             }
190             }
191             );
192              
193             my $repo = $self->config->{Repository}->{ $option{repo} };
194             my $type = $repo->{type};
195             my $repo_o = Rex::Repositorio::Repository_Factory->create(
196             type => $type,
197             options => {
198             app => $self,
199             repo => {
200             name => $option{repo},
201             %{$repo},
202             }
203             }
204             );
205              
206             $repo_o->remove_file( file => $option{file} );
207             }
208              
209             sub list {
210             my $self = shift;
211             my @repos = keys %{ $self->config->{Repository} };
212              
213             $self->_print(@repos);
214             }
215              
216             sub update_errata {
217             my $self = shift;
218             my %option = validate(
219             @_,
220             {
221             repo => {
222             type => SCALAR
223             },
224             }
225             );
226              
227             my $repo = $self->config->{Repository}->{ $option{repo} };
228             my $type = $repo->{type};
229             my $repo_o = Rex::Repositorio::Repository_Factory->create(
230             type => $type,
231             options => {
232             app => $self,
233             repo => {
234             name => $option{repo},
235             %{$repo},
236             }
237             }
238             );
239              
240             $repo_o->update_errata();
241             }
242              
243             sub print_errata {
244             my $self = shift;
245             my %option = validate(
246             @_,
247             {
248             repo => {
249             type => SCALAR
250             },
251             package => {
252             type => SCALAR
253             },
254             version => {
255             type => SCALAR
256             },
257             arch => {
258             type => SCALAR
259             },
260             }
261             );
262              
263             my $repo = $self->config->{Repository}->{ $option{repo} };
264             my $type = $repo->{type};
265             my $repo_o = Rex::Repositorio::Repository_Factory->create(
266             type => $type,
267             options => {
268             app => $self,
269             repo => {
270             name => $option{repo},
271             %{$repo},
272             }
273             }
274             );
275              
276             my $errata = $repo_o->get_errata(
277             arch => $option{arch},
278             package => $option{package},
279             version => $option{version}
280             );
281              
282             for my $pkg_version ( sort { $a cmp $b } keys %{$errata} ) {
283             print "Name : $errata->{$pkg_version}->[0]->{advisory_name}\n";
284             print "Version : $pkg_version\n";
285             print "Synopsis : $errata->{$pkg_version}->[0]->{synopsis}\n";
286             print "References : $errata->{$pkg_version}->[0]->{references}\n";
287             print "Type : $errata->{$pkg_version}->[0]->{type}\n";
288             print "\n";
289             }
290             }
291              
292             sub init {
293             my $self = shift;
294             my %option = validate(
295             @_,
296             {
297             repo => {
298             type => SCALAR
299             }
300             }
301             );
302              
303             my $repo = $self->config->{Repository}->{ $option{repo} };
304              
305             if ( !$repo ) {
306             $self->logger->error("Repository $option{repo} not found.");
307             confess "Repository $option{repo} not found.";
308             }
309              
310             my $type = $repo->{type};
311             my $repo_o = Rex::Repositorio::Repository_Factory->create(
312             type => $type,
313             options => {
314             app => $self,
315             repo => {
316             name => $option{repo},
317             %{$repo},
318             }
319             }
320             );
321              
322             $repo_o->verify_options;
323             $repo_o->init;
324             }
325              
326             sub mirror {
327             my $self = shift;
328             my %option = validate(
329             @_,
330             {
331             repo => {
332             type => SCALAR
333             },
334             update_metadata => {
335             type => BOOLEAN,
336             optional => 1,
337             },
338             update_files => {
339             type => BOOLEAN,
340             optional => 1,
341             },
342             }
343             );
344              
345             my @repositories = ( $option{repo} );
346             if ( $option{repo} eq "all" ) {
347             @repositories = keys %{ $self->config->{Repository} };
348             }
349              
350             for my $repo (@repositories) {
351             my $type = $self->config->{Repository}->{$repo}->{type};
352              
353             my $repo_o = Rex::Repositorio::Repository_Factory->create(
354             type => $type,
355             options => {
356             app => $self,
357             repo => {
358             name => $repo,
359             %{ $self->config->{Repository}->{$repo} },
360             }
361             }
362             );
363              
364             $repo_o->mirror(
365             update_metadata => $option{update_metadata},
366             update_files => $option{update_files}
367             );
368             }
369             }
370              
371             sub tag {
372             my $self = shift;
373             my %option = validate(
374             @_,
375             {
376             repo => {
377             type => SCALAR
378             },
379             tag => {
380             type => SCALAR
381             },
382             }
383             );
384              
385             my $repo_config = $self->config->{Repository}->{ $option{repo} };
386             my $root_dir = $self->config->{RepositoryRoot};
387             $repo_config->{local} =~ s/\/$//;
388              
389             my @dirs = ("$root_dir/head/$repo_config->{local}");
390             my $tag_dir = "$root_dir/$option{tag}/$repo_config->{local}";
391              
392             mkpath $tag_dir;
393              
394             for my $dir (@dirs) {
395             opendir my $dh, $dir;
396             while ( my $entry = readdir $dh ) {
397             next if ( $entry eq "." || $entry eq ".." );
398             my $rel_entry = "$dir/$entry";
399             $rel_entry =~ s/$root_dir\/head\/$repo_config->{local}\///;
400              
401             if ( -d "$dir/$entry" ) {
402             push @dirs, "$dir/$entry";
403             $self->logger->debug("Creating directory: $tag_dir/$rel_entry.");
404             mkdir "$tag_dir/$rel_entry";
405             next;
406             }
407              
408             $self->logger->debug(
409             "Linking (hard): $dir/$entry -> $tag_dir/$rel_entry");
410             link "$dir/$entry", "$tag_dir/$rel_entry";
411             }
412             closedir $dh;
413             }
414             }
415              
416             sub get_errata_dir {
417             my $self = shift;
418             my %option = validate(
419             @_,
420             {
421             repo => {
422             type => SCALAR
423             },
424             tag => {
425             type => SCALAR
426             }
427             }
428             );
429              
430             return File::Spec->catdir(
431             File::Spec->rel2abs( $self->config->{RepositoryRoot} ),
432             $option{tag}, $option{repo}, "errata" );
433             }
434              
435             sub get_repo_dir {
436             my $self = shift;
437             my %option = validate(
438             @_,
439             {
440             repo => {
441             type => SCALAR
442             }
443             }
444             );
445              
446             return File::Spec->rel2abs( $self->config->{RepositoryRoot}
447             . "/head/"
448             . $self->config->{Repository}->{ $option{repo} }->{local} );
449             }
450              
451             sub progress_bar {
452             my $self = shift;
453             my %option = validate(
454             @_,
455             {
456             title => {
457             type => SCALAR,
458             },
459             length => {
460             type => SCALAR,
461             }
462             }
463             );
464              
465             $self->print_info( $option{title} );
466             print "\n";
467              
468             my $pr = Term::ProgressBar->new( { count => $option{length} } );
469             return $pr;
470             }
471              
472             sub print_info {
473             my ( $self, $msg ) = @_;
474             print color "bold green";
475             print ">> ";
476             print color "reset";
477              
478             my @parts = split( / /, $msg );
479             my $current_line_len = 3;
480              
481             for my $part (@parts) {
482             $current_line_len += length $part;
483             if ( $current_line_len >= 80 ) {
484             print "\n ";
485             $current_line_len = 3;
486             }
487              
488             print "$part ";
489             }
490              
491             print "\n";
492             }
493              
494             sub _print {
495             my $self = shift;
496             my @lines = @_;
497              
498             print "repositorio: $VERSION\n";
499             print "-" x 80;
500             print "\n";
501             print "$_\n" for @lines;
502             }
503              
504             sub _help {
505             my ($self) = @_;
506              
507             $self->_print(
508             "--mirror mirror a configured repository (needs --repo)",
509             "--tag=tagname tag a repository (needs --repo)",
510             "--repo=reponame the name of the repository to use",
511             "--update-metadata update the metadata of a repository",
512             "--update-files download files even if they are already downloaded",
513             "--init initialize an empty repository",
514             "--add-file=file add a file to a repository (needs --repo)",
515             "--remove-file=file remove a file from a repository (needs --repo)",
516             "--list list known repositories",
517             "--server start a server for file delivery. (not available for all repository types)",
518             "--update-errata updates the errata database for a repo (needs --repo)",
519             "--errata query errata for a package (needs --repo, --package, --version, --arch)",
520             " --package=pkg for which package the errata should be queries",
521             " --version=ver for which version of a package the errata should be queries",
522             " --arch=arch for which architecture of a package the errata should be queries",
523             "--help display this help message",
524             );
525              
526             }
527              
528             1;
529              
530             __END__
531              
532             # ABSTRACT: repositor.io is a tool to create and manage linux repositories.
533              
534             =pod
535              
536             =head1 repositor.io - Linux Repository Management
537              
538             repositor.io is a tool to create and manage linux repositories.
539             You can mirror online repositories so that you don't need to download the
540             package every time you set up a new server. You can also secure your servers
541             behind a firewall and disable outgoing http traffic.
542              
543             With repositor.io it is easy to create custom repositories for your own
544             packages. With the integration of a configuration management tool you can
545             create consistant installations of your server.
546              
547             =head2 GETTING HELP
548              
549             =over 4
550              
551             =item * Web Site: L<http://repositor.io/>
552              
553             =item * IRC: irc.freenode.net #rex (RexOps IRC Channel)
554              
555             =item * Bug Tracker: L<https://github.com/RexOps/repositorio/issues>
556              
557             =item * Twitter: L<http://twitter.com/RexOps>
558              
559             =back
560              
561             =head2 COMMAND LINE
562              
563             =over 4
564              
565             =item --mirror mirror a configured repository (needs --repo)
566              
567             =item --tag=tagname tag a repository (needs --repo)
568              
569             =item --repo=reponame the name of the repository to use
570              
571             =item --update-metadata update the metadata of a repository
572              
573             =item --update-files download files even if they are already downloaded
574              
575             =item --init initialize an empty repository
576              
577             =item --add-file=file add a file to a repository (needs --repo)
578              
579             =item --remove-file=file remove a file from a repository (needs --repo)
580              
581             =item --list list known repositories
582              
583             =item --server start a server for file delivery. (not available for all repository types)
584              
585             =item --update-errata updates the errata database for a repo (needs --repo)",
586              
587             =item --errata query errata for a package (needs --repo, --package, --version, --arch)",
588              
589             =item --package=pkg for which package the errata should be queries",
590              
591             =item --version=ver for which version of a package the errata should be queries",
592              
593             =item --arch=arch for which architecture of a package the errata should be queries",
594              
595             =item --help display this help message
596              
597             =back
598              
599             =head2 CONFIGURATION
600              
601             To configure repositor.io create a configuration file
602             I</etc/rex/repositorio.conf>.
603             RepositoryRoot = /srv/html/repo/
604            
605             # log4perl configuration file
606             <Log4perl>
607             config = /etc/rex/io/log4perl.conf
608             </Log4perl>
609            
610             # create a mirror of the nightly rex repository
611             # the files will be stored in
612             # /srv/html/repo/head/rex-centos-6-x86-64/CentOS/6/rex/x86_64/
613             <Repository rex-centos-6-x86-64>
614             url = http://nightly.rex.linux-files.org/CentOS/6/rex/x86_64/
615             local = rex-centos-6-x86-64/CentOS/6/rex/x86_64/
616             type = Yum
617             </Repository>
618            
619             # create a mirror of centos 6
620             # and download the pxe boot files, too.
621             <Repository centos-6-x86-64>
622             url = http://ftp.hosteurope.de/mirror/centos.org/6/os/x86_64/
623             local = centos-6-x86-64/CentOS/6/os/x86_64/
624             type = Yum
625             images = true
626             </Repository>
627            
628             # create a custom repository
629             <Repository centos-6-x86-64-mixed>
630             local = centos-6-x86-64-mixed/mixed/6/x86_64/
631             type = Yum
632             </Repository>
633            
634             <Repository debian-wheezy-i386-main>
635             url = http://ftp.de.debian.org/debian/
636             local = debian-wheezy-amd64-main/debian
637             type = Apt
638             arch = i386
639             dist = wheezy
640             component = main
641             </Repository>
642            
643             If you want to sign your custom repositories you have to configure the gpg key to use.
644             repositorio automatically exports the public key into the root of the repository, so it can be imported from the clients.
645             If you don't specify the gpg password repositorio will ask you for the password.
646              
647             An example for YUM repositories:
648              
649             <Repository centos-6-x86-64-mixed>
650             local = centos-6-x86-64-mixed/mixed/6/x86_64/
651             type = Yum
652             <gpg>
653             key = DA95F273
654             password = test
655             </gpg>
656             </Repository>
657              
658             An example for APT repositories:
659              
660             <Repository debian-7-x86-64-mixed>
661             local = debian-7-x86-64-mixed/debian
662             type = Apt
663             arch = amd64
664             dist = wheezy
665             component = mixed
666             <gpg>
667             key = DA95F273
668             password = test
669             </gpg>
670             </Repository>
671              
672             An example log4perl.conf file:
673              
674             log4perl.rootLogger = DEBUG, FileAppndr1
675              
676             log4perl.appender.FileAppndr1 = Log::Log4perl::Appender::File
677             log4perl.appender.FileAppndr1.filename = /var/log/repositorio.log
678             log4perl.appender.FileAppndr1.layout = Log::Log4perl::Layout::SimpleLayout