package RPM::Packager; use strict; use warnings; use Data::Dumper; use File::Temp; use File::Path qw(make_path); use Cwd; use Expect; use RPM::Packager::Utils; =head1 NAME RPM::Packager - Manifest-based approach for building RPMs =head1 VERSION Version 0.3.5 =cut our $VERSION = 'v0.3.5'; =head1 SYNOPSIS Building RPMs should be easy. This is a manifest approach to easily create custom RPMs. Once this module is installed, building RPMs should be as simple as writing a YAML file that looks like the following: --- name: testpackage version: grep Changelog # version string or some command to retrieve it os: el6 # optional, don't specify anything if package is os-independent dependencies: - perl-YAML > 0.5 - perl-JSON files: bin: /usr/local/bin # directory-based mapping. RPM will install CWD/bin/* to /usr/local/bin. user: apache # specify the owner of files. default: root group: apache # specify the group owner of files. default: root sign: # optionally, gpg signing of RPM gpg_name: ED16CAB # provide the GPG key ID passphrase_cmd: cat secret_file # command to retrieve the secret after_install: path/to/script # shellscript to run after the package is installed (%post) architecture: noarch # specify the architecture for this package (default: x86_64) Then run: rpm_packager.pl Note : You need to have fpm available in PATH. For GPG signing, you need to have proper keys imported. Note2: The 'release' field of RPM will be determined by the BUILD_NUMBER env variable plus 'os' field, like '150.el7'. If BUILD_NUMBER is unavailable, 1 will be used. If os is unspecified, nothing will be appended. You may also interact with the library directly as long as you pass in the manifest information in a hash: use RPM::Packager; my %args = ( name => 'testpackage', version => 'grep Changelog', files => { bin => '/usr/local/bin' }, dependencies => [ 'perl-YAML > 0.5', 'perl-JSON' ], os => 'el6', user => 'apache', group => 'apache', sign => { 'gpg_name' => 'ED16CAB', 'passphrase_cmd' => 'cat secret_file' }, after_install => 'foo/bar/baz.sh', architecture => 'noarch' ); my $obj = RPM::Packager->new(%args); $obj->create_rpm(); # RPM produced in CWD =head1 SUBROUTINES/METHODS =head2 new(%args) Constructor. Pass in a hash containing manifest info. =cut sub new { my ( $class, %args ) = @_; chomp( my $fpm = `which fpm 2>/dev/null` ); chomp( my $cp = `which cp` ); my $self = { fpm => $fpm, cp => $cp, cwd => getcwd(), tempdir => File::Temp->newdir(), %args }; return bless $self, $class; } sub find_version { my $self = shift; my $value = $self->{version}; ( RPM::Packager::Utils::is_command($value) ) ? RPM::Packager::Utils::eval_command($value) : $value; } sub generate_dependency_opts { my $self = shift; my $dependencies = $self->{dependencies} || []; my @chunks; for my $dependency ( @{$dependencies} ) { push @chunks, "-d '$dependency'"; } return join( " ", @chunks ); } sub generate_user_group { my $self = shift; my $user = $self->{user} || 'root'; my $group = $self->{group} || 'root'; return ( $user, $group ); } sub copy_to_tempdir { my $self = shift; my $cwd = $self->{cwd}; my %hash = %{ $self->{files} }; my $tempdir = $self->{tempdir}; for my $key ( keys %hash ) { my $dst = $hash{$key}; my $target_dir = "$tempdir$dst"; make_path($target_dir); system("$self->{cp} -r $cwd/$key/* $target_dir"); } return 1; } sub add_gpg_opts { my $self = shift; return unless ( $self->should_gpgsign() ); my $gpg_name = $self->{sign}->{gpg_name}; my $passphrase_cmd = $self->{sign}->{passphrase_cmd}; my $opts = $self->{opts} || []; push @{$opts}, '--rpm-sign', '--rpm-rpmbuild-define', "'_gpg_name $gpg_name'"; $self->{opts} = $opts; $self->{gpg_passphrase} = RPM::Packager::Utils::eval_command($passphrase_cmd); } sub add_after_install { my $self = shift; return unless ( $self->{after_install} ); my $opts = $self->{opts} || []; push @{$opts}, '--after-install', $self->{after_install}; $self->{opts} = $opts; } sub populate_opts { my $self = shift; my $version = $self->find_version(); my $release = $ENV{BUILD_NUMBER} || 1; my $os = $self->{os}; my $iteration = ($os) ? "$release.$os" : $release; my $dependency_opts = $self->generate_dependency_opts(); my $architecture = $self->{architecture} || 'x86_64'; my ( $user, $group ) = $self->generate_user_group(); my @opts = ( $self->{fpm}, '-v', $version, '--rpm-user', $user, '--rpm-group', $group, '--iteration', $iteration, '-n', $self->{name}, $dependency_opts, '-s', 'dir', '-t', 'rpm', '-a', $architecture, '-C', $self->{tempdir} ); $self->{opts} = [@opts]; $self->add_gpg_opts(); $self->add_after_install(); push @{ $self->{opts} }, '.'; # relative to the temporary directory } sub handle_interactive_prompt { my $self = shift; my $opts = $self->{opts}; my $cmd = join( ' ', @{$opts} ); my $pass = $self->{gpg_passphrase}; my $exp = Expect->new(); $exp->spawn($cmd); $exp->expect( undef, [ qr/Enter pass phrase:/i => sub { my $exp = shift; $exp->send("$pass\n"); exp_continue; } ] ); return 1; } sub should_gpgsign { my $self = shift; my $gpg_name = $self->{sign}->{gpg_name}; my $passphrase_cmd = $self->{sign}->{passphrase_cmd}; ( $gpg_name && $passphrase_cmd ) ? 1 : 0; } =head2 create_rpm Creates RPM based on the information in the object =cut sub create_rpm { my $self = shift; $self->copy_to_tempdir(); $self->populate_opts(); if ( $self->should_gpgsign() ) { $self->handle_interactive_prompt(); } else { my $cmd = join( ' ', @{ $self->{opts} } ); system($cmd); } return 1; } =head1 AUTHOR Satoshi Yagi, C<< >> =head1 BUGS Please report any bugs or feature requests to C, or through the web interface at L. I will be notified, and then you'll automatically be notified of progress on your bug as I make changes. =head1 SUPPORT You can find documentation for this module with the perldoc command. perldoc RPM::Packager You can also look for information at: =over 4 =item * RT: CPAN's request tracker (report bugs here) L =item * AnnoCPAN: Annotated CPAN documentation L =item * CPAN Ratings L =item * Search CPAN L =back =head1 ACKNOWLEDGEMENTS =head1 LICENSE AND COPYRIGHT Copyright 2016 Satoshi Yagi. This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see L. =cut 1; # End of RPM::Packager