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