File Coverage

blib/lib/OTRS/OPM/Installer.pm
Criterion Covered Total %
statement 21 23 91.3
branch n/a
condition n/a
subroutine 8 8 100.0
pod n/a
total 29 31 93.5


line stmt bran cond sub pod time code
1             package OTRS::OPM::Installer;
2             $OTRS::OPM::Installer::VERSION = '0.02';
3             # ABSTRACT: Install OTRS add ons
4              
5 1     1   64981 use v5.10;
  1         11  
6              
7 1     1   5 use strict;
  1         2  
  1         24  
8 1     1   5 use warnings;
  1         2  
  1         24  
9              
10 1     1   323 use Moo;
  1         7926  
  1         5  
11 1     1   1381 use IO::All;
  1         10383  
  1         9  
12 1     1   581 use Capture::Tiny qw(:all);
  1         19440  
  1         146  
13 1     1   371 use Types::Standard qw(ArrayRef Str);
  1         64606  
  1         14  
14              
15 1     1   1296 use OTRS::OPM::Parser;
  0            
  0            
16              
17             use OTRS::OPM::Installer::Types;
18             use OTRS::OPM::Installer::Utils::OTRS;
19             use OTRS::OPM::Installer::Utils::File;
20             use OTRS::OPM::Installer::Logger;
21              
22             has package => ( is => 'ro', isa => Str );
23             has otrs_version => ( is => 'ro', isa => Str, lazy => 1, default => \&_build_otrs_version );
24             has prove => ( is => 'ro', default => sub { 0 } );
25             has manager => ( is => 'ro', lazy => 1, default => \&_build_manager );
26             has conf => ( is => 'ro' );
27             has sudo => ( is => 'ro' );
28             has utils_otrs => ( is => 'ro', lazy => 1, default => sub{ OTRS::OPM::Installer::Utils::OTRS->new } );
29             has verbose => ( is => 'ro', default => sub { 0 } );
30             has logger => ( is => 'ro', lazy => 1, default => sub { OTRS::OPM::Installer::Logger->new } );
31              
32             sub install {
33             my $self = shift;
34              
35             if ( @_ % 2 ) {
36             unshift @_, 'package';
37             }
38              
39             my %params = @_;
40              
41             my %file_opts;
42             if ( $params{repositories} and ref $params{repositories} eq 'ARRAY' ) {
43             $file_opts{repositories} = $params{repositories};
44             }
45             if ( $params{version} and $params{version_exact} ) {
46             $file_opts{version} = $params{version};
47             }
48              
49             say sprintf "Try to install %s...", $params{package} || $self->package if $self->verbose;
50            
51             my $package_utils = OTRS::OPM::Installer::Utils::File->new(
52             %file_opts,
53             package => $params{package} || $self->package,
54             otrs_version => $self->otrs_version,
55             );
56              
57             my $package_path = $package_utils->resolve_path;
58              
59             if ( !$package_path ) {
60             my $message = sprintf "Could not find a .opm file for %s%s (OTRS version %s)",
61             $params{package} || $self->package,
62             ( $file_opts{version} ? " $file_opts{version}" : "" ),
63             $self->otrs_version;
64              
65             $self->logger->error( fatal => $message );
66             die $message;
67             }
68              
69             my $parsed = OTRS::OPM::Parser->new(
70             opm_file => $package_path,
71             );
72              
73             $parsed->parse;
74              
75             if ( $parsed->error_string ) {
76             my $message = sprintf "Cannot parse $package_path: %s", $parsed->error_string;
77             $self->logger->error( fatal => $message );
78             die $message;
79             }
80              
81             if ( !$self->_check_matching_versions( $parsed, $self->otrs_version ) ) {
82             my $message = sprintf 'framework versions of %s (%s) doesn\'t match otrs version %s',
83             $parsed->name,
84             join ( ', ', $parsed->framework ),
85             $self->otrs_version;
86              
87             $self->logger->error( fatal => $message );
88             die $message;
89             }
90              
91             if ( $self->utils_otrs->is_installed( package => $parsed->name, version => $parsed->version ) ) {
92             my $message = sprintf 'Addon %s is up to date (%s)',
93             $parsed->name, $parsed->version;
94              
95             $self->logger->debug( message => $message );
96             say $message;
97             exit 0;
98             }
99              
100             say sprintf "Working on %s...", $parsed->name if $self->verbose;
101             $self->logger->debug( message => sprintf "Working on %s...", $parsed->name );
102              
103             my @dependencies = $parsed->dependencies;
104             my @cpan_deps = grep{ $_->{type} eq 'CPAN' }@dependencies;
105             my @otrs_deps = grep{ $_->{type} eq 'OTRS' }@dependencies;
106              
107             my $found_dependencies = join ', ', map{ $_->{name} }@dependencies;
108             say sprintf "Found dependencies: %s", $found_dependencies if $self->verbose;
109             $self->logger->debug( message => sprintf "Found dependencies: %s", $found_dependencies );
110              
111             for my $cpan_dep ( @cpan_deps ) {
112             my $module = $cpan_dep->{name};
113             my $version = $cpan_dep->{version};
114              
115             eval "use $module $version; 1;" and next;
116              
117             $self->_cpan_install( %{$cpan_dep} );
118             }
119              
120             for my $otrs_dep ( @otrs_deps ) {
121             my $module = $otrs_dep->{name};
122             my $version = $otrs_dep->{version};
123              
124             $self->utils_otrs->is_installed( %{$otrs_dep} ) and next;
125              
126             $self->install( package => $module, version => $version );
127             }
128              
129             if ( $self->prove ) {
130             # TODO: run unittests
131             }
132              
133             my $content = io( $package_path )->slurp;
134              
135             my $message = sprintf "Install %s ...", $parsed->name;
136             say $message if $self->verbose;
137             $self->logger->debug( message => $message );
138              
139             $self->manager->PackageInstall( String => $content );
140             }
141              
142             sub _cpan_install {
143             my ( $self, %params) = @_;
144              
145             my $dist = $params{name};
146             my @sudo = $self->sudo ? 'sudo' : ();
147             my ($out, $err, $exit) = capture {
148             system @sudo, 'cpanm', $dist;
149             };
150              
151             if ( $out !~ m{Successfully installed } ) {
152             die "Installation of dependency failed ($dist)! - ($err)";
153             }
154              
155             return;
156             }
157              
158             sub _build_manager {
159             my $self = shift;
160              
161             return $self->utils_otrs->manager;
162             }
163              
164             sub _build_utils_otrs {
165             OTRS::OPM::Installer::Utils::OTRS->new;
166             }
167              
168             sub _build_otrs_version {
169             shift->utils_otrs->otrs_version;
170             }
171              
172             sub _check_matching_versions {
173             my ($self, $parsed, $otrs_version) = @_;
174              
175             my ($major, $minor, $patch) = split /\./, $otrs_version;
176              
177             my $check_ok;
178              
179             FRAMEWORK:
180             for my $required_framework ( $parsed->framework ) {
181             my ($r_major, $r_minor, $r_patch) = split /\./, $required_framework;
182              
183             next FRAMEWORK if $r_major != $major;
184             next FRAMEWORK if lc $r_minor ne 'x' && $r_minor != $minor;
185             next FRAMEWORK if lc $r_patch ne 'x' && $r_patch != $patch;
186              
187             $check_ok = 1;
188             last FRAMEWORK;
189             }
190              
191             return $check_ok;
192             }
193              
194             1;
195              
196             __END__
197              
198             =pod
199              
200             =encoding UTF-8
201              
202             =head1 NAME
203              
204             OTRS::OPM::Installer - Install OTRS add ons
205              
206             =head1 VERSION
207              
208             version 0.02
209              
210             =head1 SYNOPSIS
211              
212             use OTRS::OPM::Installer;
213            
214             my $installer = OTRS::OPM::Installer->new;
215             $installer->install( 'FAQ' );
216            
217             # or
218            
219             my $installer = OTRS::OPM::Installer->new();
220             $installer->install( package => 'FAQ', version => '2.1.9' );
221              
222             # provide path to a config file
223             my $installer = OTRS::OPM::Installer->new(
224             conf => 'test.rc',
225             );
226             $installer->install( 'FAQ' );
227              
228             =head1 DESCRIPTION
229              
230             This is an alternate installer for OTRS addons. The standard OTRS package manager
231             currently does not install dependencies. OTRS::OPM::Installer takes care of those
232             dependencies and it can handle dependencies from different places:
233              
234             =over 4
235              
236             =item * OTRS.org
237              
238             =over 4
239              
240             =item * ITSM Packages
241              
242             =item * Misc Packages
243              
244             =back
245              
246             =item * OPAR
247              
248             =back
249              
250             =head1 CONFIGURATION FILE
251              
252             You can provide some basic configuration in a F<.opminstaller.rc> file:
253              
254             repository=ftp://ftp.otrs.org/pub/otrs/packages
255             repository=ftp://ftp.otrs.org/pub/otrs/itsm/packages33
256             repository=http://opar.perl-services.de
257             repository=http://feature-addons.de/repo
258             otrs_path=/srv/otrs
259              
260             =head1 ACKNOWLEDGEMENT
261              
262             The development of this packages was sponsored by http://feature-addons.de
263              
264             =head1 AUTHOR
265              
266             Renee Baecker <reneeb@cpan.org>
267              
268             =head1 COPYRIGHT AND LICENSE
269              
270             This software is Copyright (c) 2017 by Renee Baecker.
271              
272             This is free software, licensed under:
273              
274             The Artistic License 2.0 (GPL Compatible)
275              
276             =cut