File Coverage

lib/RPM/Packager.pm
Criterion Covered Total %
statement 95 107 88.7
branch 9 12 75.0
condition 11 15 73.3
subroutine 18 20 90.0
pod 1 10 10.0
total 134 164 81.7


line stmt bran cond sub pod time code
1             package RPM::Packager;
2              
3 1     1   35317 use strict;
  1         1  
  1         25  
4 1     1   5 use warnings;
  1         2  
  1         26  
5 1     1   6 use Data::Dumper;
  1         6  
  1         50  
6 1     1   1210 use File::Temp;
  1         22676  
  1         78  
7 1     1   7 use File::Path qw(make_path);
  1         2  
  1         38  
8 1     1   5 use Cwd;
  1         2  
  1         54  
9 1     1   1317 use Expect;
  1         154997  
  1         81  
10 1     1   747 use RPM::Packager::Utils;
  1         3  
  1         1065  
11              
12             =head1 NAME
13              
14             RPM::Packager - Manifest-based approach for building RPMs
15              
16             =head1 VERSION
17              
18             Version 0.3.1
19              
20             =cut
21              
22             our $VERSION = 'v0.3.1';
23              
24             =head1 SYNOPSIS
25              
26             Building RPMs should be easy.
27              
28             This is a manifest approach to easily create custom RPMs. Once this module is installed, building RPMs should be as
29             simple as writing a YAML file that looks like the following:
30              
31             ---
32             name: testpackage
33             version: grep Changelog # version string or some command to retrieve it
34             os: el6 # optional, don't specify anything if package is os-independent
35             dependencies:
36             - perl-YAML > 0.5
37             - perl-JSON
38             files:
39             bin: /usr/local/bin # directory-based mapping. RPM will install CWD/bin/* to /usr/local/bin.
40             user: apache # specify the owner of files. default: root
41             group: apache # specify the group owner of files. default: root
42             sign: # optionally, gpg signing of RPM
43             gpg_name: ED16CAB # provide the GPG key ID
44             passphrase_cmd: cat secret_file # command to retrieve the secret
45             after_install: /path/to/script # script to run after the package is installed (%post)
46              
47             Then run:
48              
49             rpm_packager.pl
50              
51             Note : You need to have fpm available in PATH. For GPG signing, you need to have proper keys imported.
52              
53             Note2: The 'release' field of RPM will be determined by the BUILD_NUMBER env variable plus 'os' field, like '150.el7'.
54             If BUILD_NUMBER is unavailable, 1 will be used. If os is unspecified, nothing will be appended.
55              
56             You may also interact with the library directly as long as you pass in the manifest information in a hash:
57              
58             use RPM::Packager;
59              
60             my %args = (
61             name => 'testpackage',
62             version => 'grep Changelog',
63             files => { bin => '/usr/local/bin' },
64             dependencies => [
65             'perl-YAML > 0.5',
66             'perl-JSON'
67             ],
68             os => 'el6',
69             user => 'apache',
70             group => 'apache',
71             sign => {
72             'gpg_name' => 'ED16CAB',
73             'passphrase_cmd' => 'cat secret_file'
74             },
75             after_install => 'foo/bar/baz.sh'
76             );
77              
78             my $obj = RPM::Packager->new(%args);
79             $obj->create_rpm(); # RPM produced in CWD
80              
81             =head1 SUBROUTINES/METHODS
82              
83             =head2 new(%args)
84              
85             Constructor. Pass in a hash containing manifest info.
86              
87             =cut
88              
89             sub new {
90 15     15 1 41726 my ( $class, %args ) = @_;
91 15         123080 chomp( my $fpm = `which fpm 2>/dev/null` );
92 15         87334 chomp( my $cp = `which cp` );
93              
94 15         1084 my $self = {
95             fpm => $fpm,
96             cp => $cp,
97             cwd => getcwd(),
98             tempdir => File::Temp->newdir(),
99             %args
100             };
101 15         15009 return bless $self, $class;
102             }
103              
104             sub find_version {
105 3     3 0 799 my $self = shift;
106 3         22 my $value = $self->{version};
107 3 100       44 ( RPM::Packager::Utils::is_command($value) ) ? RPM::Packager::Utils::eval_command($value) : $value;
108             }
109              
110             sub generate_dependency_opts {
111 3     3 0 780 my $self = shift;
112 3   100     54 my $dependencies = $self->{dependencies} || [];
113 3         14 my @chunks;
114 3         12 for my $dependency ( @{$dependencies} ) {
  3         17  
115 2         14 push @chunks, "-d '$dependency'";
116             }
117 3         69 return join( " ", @chunks );
118             }
119              
120             sub generate_user_group {
121 3     3 0 892 my $self = shift;
122 3   100     51 my $user = $self->{user} || 'root';
123 3   100     44 my $group = $self->{group} || 'root';
124 3         29 return ( $user, $group );
125             }
126              
127             sub copy_to_tempdir {
128 1     1 0 18 my $self = shift;
129              
130 1         7 my $cwd = $self->{cwd};
131 1         13 my %hash = %{ $self->{files} };
  1         15  
132 1         7 my $tempdir = $self->{tempdir};
133              
134 1         11 for my $key ( keys %hash ) {
135 1         7 my $dst = $hash{$key};
136 1         21 my $target_dir = "$tempdir$dst";
137 1         914 make_path($target_dir);
138 1         9538 system("$self->{cp} -r $cwd/$key/* $target_dir");
139             }
140 1         63 return 1;
141             }
142              
143             sub add_gpg_opts {
144 3     3 0 22 my $self = shift;
145              
146 3 100       23 return unless ( $self->should_gpgsign() );
147              
148 2         8 my $gpg_name = $self->{sign}->{gpg_name};
149 2         8 my $passphrase_cmd = $self->{sign}->{passphrase_cmd};
150 2   50     30 my $opts = $self->{opts} || [];
151 2         9 push @{$opts}, '--rpm-sign', '--rpm-rpmbuild-define', "'_gpg_name $gpg_name'";
  2         28  
152 2         16 $self->{opts} = $opts;
153 2         21 $self->{gpg_passphrase} = RPM::Packager::Utils::eval_command($passphrase_cmd);
154             }
155              
156             sub add_after_install {
157 2     2 0 15 my $self = shift;
158 2 100       17 return unless ( $self->{after_install} );
159              
160 1   50     39 my $opts = $self->{opts} || [];
161 1         6 push @{$opts}, '--after-install', $self->{after_install};
  1         12  
162 1         10 $self->{opts} = $opts;
163             }
164              
165             sub populate_opts {
166 1     1 0 98 my $self = shift;
167 1         14 my $version = $self->find_version();
168 1   50     39 my $release = $ENV{BUILD_NUMBER} || 1;
169 1         7 my $os = $self->{os};
170 1 50       12 my $iteration = ($os) ? "$release.$os" : $release;
171 1         12 my $dependency_opts = $self->generate_dependency_opts();
172 1         9 my ( $user, $group ) = $self->generate_user_group();
173              
174             my @opts = (
175             $self->{fpm}, '-v', $version, '--rpm-user', $user, '--rpm-group',
176             $group, '--iteration', $iteration, '-n', $self->{name}, $dependency_opts,
177             '-s', 'dir', '-t', 'rpm', '-C', $self->{tempdir}
178 1         18 );
179              
180 1         8 $self->{opts} = [@opts];
181 1         6 $self->add_gpg_opts();
182 1         8 $self->add_after_install();
183 1         3 push @{ $self->{opts} }, '.'; # relative to the temporary directory
  1         10  
184             }
185              
186             sub handle_interactive_prompt {
187 1     1 0 134 my $self = shift;
188 1         8 my $opts = $self->{opts};
189 1         5 my $cmd = join( ' ', @{$opts} );
  1         16  
190 1         7 my $pass = $self->{gpg_passphrase};
191              
192 1         30 my $exp = Expect->new();
193 1         63674 $exp->spawn($cmd);
194             $exp->expect(
195             undef,
196             [
197             qr/Enter pass phrase:/i => sub {
198 0     0   0 my $exp = shift;
199 0         0 $exp->send("$pass\n");
200 0         0 exp_continue;
201             }
202 1         52 ]
203             );
204 1         19 return 1;
205             }
206              
207             sub should_gpgsign {
208 5     5 0 803 my $self = shift;
209 5         40 my $gpg_name = $self->{sign}->{gpg_name};
210 5         18 my $passphrase_cmd = $self->{sign}->{passphrase_cmd};
211 5 100 66     124 ( $gpg_name && $passphrase_cmd ) ? 1 : 0;
212             }
213              
214             =head2 create_rpm
215              
216             Creates RPM based on the information in the object
217              
218             =cut
219              
220             sub create_rpm {
221 0     0     my $self = shift;
222              
223 0           $self->copy_to_tempdir();
224 0           $self->populate_opts();
225              
226 0 0         if ( $self->should_gpgsign() ) {
227 0           $self->handle_interactive_prompt();
228             }
229             else {
230 0           my $cmd = join( ' ', @{ $self->{opts} } );
  0            
231 0           system($cmd);
232             }
233 0           return 1;
234             }
235              
236             =head1 AUTHOR
237              
238             Satoshi Yagi, C<< >>
239              
240             =head1 BUGS
241              
242             Please report any bugs or feature requests to C, or through
243             the web interface at L. I will be notified, and then you'll
244             automatically be notified of progress on your bug as I make changes.
245              
246              
247              
248              
249             =head1 SUPPORT
250              
251             You can find documentation for this module with the perldoc command.
252              
253             perldoc RPM::Packager
254              
255              
256             You can also look for information at:
257              
258             =over 4
259              
260             =item * RT: CPAN's request tracker (report bugs here)
261              
262             L
263              
264             =item * AnnoCPAN: Annotated CPAN documentation
265              
266             L
267              
268             =item * CPAN Ratings
269              
270             L
271              
272             =item * Search CPAN
273              
274             L
275              
276             =back
277              
278              
279             =head1 ACKNOWLEDGEMENTS
280              
281              
282             =head1 LICENSE AND COPYRIGHT
283              
284             Copyright 2015 Satoshi Yagi.
285              
286             This program is free software; you can redistribute it and/or modify it
287             under the terms of the the Artistic License (2.0). You may obtain a
288             copy of the full license at:
289              
290             L
291              
292             Any use, modification, and distribution of the Standard or Modified
293             Versions is governed by this Artistic License. By using, modifying or
294             distributing the Package, you accept this license. Do not use, modify,
295             or distribute the Package, if you do not accept this license.
296              
297             If your Modified Version has been derived from a Modified Version made
298             by someone other than you, you are nevertheless required to ensure that
299             your Modified Version complies with the requirements of this license.
300              
301             This license does not grant you the right to use any trademark, service
302             mark, tradename, or logo of the Copyright Holder.
303              
304             This license includes the non-exclusive, worldwide, free-of-charge
305             patent license to make, have made, use, offer to sell, sell, import and
306             otherwise transfer the Package with respect to any patent claims
307             licensable by the Copyright Holder that are necessarily infringed by the
308             Package. If you institute patent litigation (including a cross-claim or
309             counterclaim) against any party alleging that the Package constitutes
310             direct or contributory patent infringement, then this Artistic License
311             to you shall terminate on the date that such litigation is filed.
312              
313             Disclaimer of Warranty: THE PACKAGE IS PROVIDED BY THE COPYRIGHT HOLDER
314             AND CONTRIBUTORS "AS IS' AND WITHOUT ANY EXPRESS OR IMPLIED WARRANTIES.
315             THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR
316             PURPOSE, OR NON-INFRINGEMENT ARE DISCLAIMED TO THE EXTENT PERMITTED BY
317             YOUR LOCAL LAW. UNLESS REQUIRED BY LAW, NO COPYRIGHT HOLDER OR
318             CONTRIBUTOR WILL BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, OR
319             CONSEQUENTIAL DAMAGES ARISING IN ANY WAY OUT OF THE USE OF THE PACKAGE,
320             EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
321              
322              
323             =cut
324              
325             1; # End of RPM::Packager