You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1241 lines
35 KiB
1241 lines
35 KiB
#!/usr/bin/perl -w
|
|
|
|
# release-tool.pl - script to manage the Koha release process
|
|
#
|
|
# Copyright (C) 2012 C & P Bibliography Services
|
|
#
|
|
# This program is free software: you can redistribute it and/or modify
|
|
# it under the terms of the GNU Affero 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 Affero General Public License for more details.
|
|
#
|
|
# You should have received a copy of the GNU Affero General Public License
|
|
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
=head1 NAME
|
|
|
|
release-tool.pl
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
release-tool.pl
|
|
release-tool.pl --version 3.06.05
|
|
|
|
=head1 DESCRIPTION
|
|
|
|
This script takes care of most of the Koha release process, as it is
|
|
done by Jared Camins-Esakov and C & P Bibliography Services. This may
|
|
not perfectly meet the needs of other Release Maintainers and/or
|
|
organizations.
|
|
|
|
=cut
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Getopt::Long;
|
|
use Pod::Usage;
|
|
use File::Spec;
|
|
use File::Copy;
|
|
use Data::Dumper;
|
|
use File::Basename;
|
|
use File::Path qw/make_path remove_tree/;
|
|
use Term::ANSIColor;
|
|
use Time::HiRes qw/time/;
|
|
use POSIX qw/strftime ceil/;
|
|
use TAP::Harness;
|
|
use DBI;
|
|
use MIME::Lite;
|
|
use Template;
|
|
use Config::Simple;
|
|
use Net::OpenSSH;
|
|
|
|
$SIG{INT} = \&interrupt;
|
|
|
|
sub usage {
|
|
pod2usage( -verbose => 2 );
|
|
exit;
|
|
}
|
|
|
|
$| = 1;
|
|
$Term::ANSIColor::AUTORESET = 1;
|
|
|
|
my %defaults = (
|
|
alert => '',
|
|
autoversion => 0,
|
|
branch => '',
|
|
'build-result' => '',
|
|
clean => 0,
|
|
deploy => 0,
|
|
'email-file' => '',
|
|
errorlog => '',
|
|
kohaclone => '',
|
|
'maintainer-email' => '',
|
|
'maintainer-name' => '',
|
|
package => '',
|
|
'post-deploy-script' => [ '' ],
|
|
quiet => 0,
|
|
rnotes => '',
|
|
sign => 0,
|
|
since => '',
|
|
'skip-deb' => 0,
|
|
'skip-install' => 0,
|
|
'skip-marc21' => 0,
|
|
'skip-normarc' => 0,
|
|
'skip-pbuilder' => 0,
|
|
'skip-rnotes' => 0,
|
|
'skip-stats' => 0,
|
|
'skip-tests' => 0,
|
|
'skip-tgz' => 0,
|
|
'skip-unimarc' => 0,
|
|
'skip-webinstall' => 0,
|
|
stats => '',
|
|
tag => 0,
|
|
tarball => '',
|
|
'use-dist-rnotes' => 0,
|
|
verbose => 0,
|
|
version => '',
|
|
|
|
# database settings
|
|
database => $ENV{KOHA_DATABASE} || 'koha',
|
|
user => $ENV{KOHA_USER} || 'kohaadmin',
|
|
password => $ENV{KOHA_PASS} || 'katikoan',
|
|
|
|
# announcement settings
|
|
'email-template' => 'announcement.eml.tt',
|
|
'email-recipients' =>
|
|
'koha@lists.katipo.co.nz, koha-devel@lists.koha-community.org',
|
|
'email-subject' => "New Koha version",
|
|
'website-template' => 'announcement.html.tt',
|
|
);
|
|
|
|
my $deployed = 'no';
|
|
my $signed_tarball = 'no';
|
|
my $signed_packages = 'no';
|
|
my $tagged = 'no';
|
|
my $cleaned = 'no';
|
|
my $pushed = 'no';
|
|
my $skipped = '';
|
|
my $finished_tests = 'no';
|
|
my $built_tarball = 'no';
|
|
my $built_packages = 'no';
|
|
my $version_mismatch;
|
|
my $output;
|
|
my @tested_tarball_installs;
|
|
my @tested_package_installs;
|
|
my %cmdline;
|
|
my $config = new Config::Simple( syntax => 'http' );
|
|
my $log = '';
|
|
my $lxc_host = '';
|
|
|
|
=head2 General options
|
|
|
|
=over 8
|
|
|
|
=item B<--help>
|
|
|
|
Prints this help
|
|
|
|
=item B<--quiet, -q>
|
|
|
|
Don't display any status information while running. When specified
|
|
twice, also suppress the summary.
|
|
|
|
=item B<--verbose, -v>
|
|
|
|
Provide verbose diagnostic information
|
|
|
|
=item B<--config>
|
|
|
|
Read configuration settings from the specified file. Options set on the
|
|
command line will override options in the configuration file.
|
|
|
|
=back
|
|
|
|
=head2 Action control options
|
|
|
|
=over 8
|
|
|
|
=item B<--clean, -c>
|
|
|
|
Delete all the files created in the course of the test
|
|
|
|
=item B<--deploy, -d>
|
|
|
|
Deploy the package to the apt repository
|
|
|
|
=item B<--release>
|
|
|
|
Equivalent to I<--sign --deploy --tag --tarball=koha-${VERSION}.tar.gz>
|
|
|
|
=item B<--sign, -s>
|
|
|
|
Sign the tarball and package and tag (if created)
|
|
|
|
=item B<--tag, -g>
|
|
|
|
Tag the git repository
|
|
|
|
=item B<--push, -p>
|
|
|
|
Push the branch to the specified remote(s).
|
|
|
|
=item B<--skip-THING>
|
|
|
|
Most actions are performed automatically, unless the user requests that they
|
|
be skipped. Skip THING. Currently the following can be skipped:
|
|
|
|
=over 4
|
|
|
|
=item B<tests>
|
|
Unit tests
|
|
|
|
=item B<deb>
|
|
Debian package-related tasks
|
|
|
|
=item B<tgz>
|
|
Tarball-related tasks
|
|
|
|
=item B<install>
|
|
Installation-related tasks
|
|
|
|
=item B<marc21>
|
|
MARC21 instance installation
|
|
|
|
=item B<unimarc>
|
|
UNIMARC instance installation
|
|
|
|
=item B<normarc>
|
|
NORMARC instance installation
|
|
|
|
=item B<webinstall>
|
|
Running the webinstaller
|
|
|
|
=item B<pbuilder>
|
|
Updating the pbuilder environment
|
|
|
|
=item B<rnotes>
|
|
Generating release notes
|
|
|
|
=item B<stats>
|
|
Generating git statistics
|
|
|
|
=back
|
|
|
|
=back
|
|
=cut
|
|
|
|
=head2 Source description options
|
|
|
|
=over 8
|
|
|
|
=item B<--kohaclone, -k>
|
|
|
|
Kohaclone directory. Defaults to the current working directory
|
|
|
|
=item B<--branch>
|
|
|
|
The name of the git branch in use. Defaults to current git branch
|
|
|
|
=item B<--distribution>
|
|
|
|
The name of the distribution. Defaults to the current git branch
|
|
|
|
=item B<--version>
|
|
|
|
The version of Koha that is being created. Defaults to the version listed in
|
|
kohaversion.pl
|
|
|
|
=item B<--autoversion, -a>
|
|
|
|
Automatically include the git commit id and timestamp in the package version
|
|
|
|
=item B<--since>
|
|
|
|
Tag or commit from which to generate release notes and statistics. Defaults
|
|
to last tag on branch.
|
|
|
|
=back
|
|
|
|
=head2 Execution options
|
|
|
|
=over 8
|
|
|
|
=item B<--database>
|
|
|
|
Name of the MySQL database to use for tarball installs. Defaults to koharel
|
|
|
|
=item B<--user>
|
|
|
|
Name of the MySQL user for tarball installs. Defaults to koharel
|
|
|
|
=item B<--password>
|
|
|
|
Name of the MySQL password for tarball installs. Defaults to koharel
|
|
|
|
=item B<--maintainer-name>
|
|
|
|
The name of the maintainer. Defaults to global git config user.name
|
|
|
|
=item B<--maintainer-email>
|
|
|
|
The e-mail address of the maintainer. Defaults to the value of git config
|
|
--global user.email
|
|
|
|
=item B<--use-dist-rnotes>
|
|
|
|
Use the release notes included in the distribution. I<--rnotes> moust be
|
|
specified if this option is used.
|
|
|
|
=item B<--post-deploy-script>
|
|
|
|
Run the specified script at the end of the deploy phase with the summary
|
|
config file as an argument.
|
|
|
|
=back
|
|
|
|
=head2 Output options
|
|
|
|
=over 8
|
|
|
|
=item B<--build-result, -b>
|
|
|
|
Directory to put the output into. Defaults to ~/releases/[branch]/[version]
|
|
|
|
=item B<--errorlog>
|
|
|
|
File to store error information in. Defaults to [build-result]/errors.log
|
|
|
|
=item B<--rnotes, -r>
|
|
|
|
The name of the release notes file to generate or use (see I<--use-dist-rnotes>).
|
|
Defaults to [build-result]/release_notes.txt
|
|
|
|
=item B<--stats>
|
|
|
|
The base name of the files into which statistics should be placed.
|
|
Defaults to [build-result]/statistics (statistics will be generated
|
|
in .txt and .html format)
|
|
|
|
=item B<--tarball, -t>
|
|
|
|
The name of the tarball file to generate. Defaults to
|
|
[build-result]/koha-[branch]-[version].tar.gz
|
|
|
|
=item B<--alert>
|
|
|
|
E-mail address to which an an alert summarizing the result should be sent.
|
|
|
|
=item B<--email-template>
|
|
|
|
Template file for the release announcement e-mail. Defaults to
|
|
"announcement.eml.tt" in the same directory as this script
|
|
|
|
=item B<--bzlogin>
|
|
|
|
Your login on bugzilla. If provided, it will be used to retrieve
|
|
the comment 0 for each enhancement. This information will be
|
|
added in the release notes
|
|
|
|
=item B<--bzpassword>
|
|
|
|
Your password on bugzilla. Must be provided if you provide bzlogin
|
|
|
|
=back
|
|
|
|
=head2 Announcement options
|
|
|
|
=over 8
|
|
|
|
=item B<--email-recipients>
|
|
|
|
Who to generate the e-mail announcement for. Defaults to
|
|
"koha@lists.katipo.co.nz, koha-devel@lists.koha-community.org"
|
|
|
|
=item B<--email-subject>
|
|
|
|
Subject of the generated e-mail announcement. Defaults to "New Koha version"
|
|
|
|
=item B<--email-file>
|
|
|
|
File to store the generated e-mail announcement in. Defaults to
|
|
[build-result]/announcement.eml
|
|
|
|
=item B<--email-template>
|
|
|
|
Template file for the release announcement e-mail. Defaults to
|
|
"announcement.eml.tt" in the same directory as this script
|
|
|
|
=back
|
|
=cut
|
|
|
|
my $options = GetOptions(
|
|
\%cmdline,
|
|
|
|
# General options
|
|
'help|h', 'quiet|q+',
|
|
'verbose|v+', 'config=s',
|
|
|
|
# Action control options
|
|
'clean|c', 'deploy|d',
|
|
'push|p:s@', 'release',
|
|
'sign|s', 'tag|g',
|
|
'skip-tests',
|
|
'skip-deb', 'skip-tgz',
|
|
'skip-install', 'skip-marc21',
|
|
'skip-unimarc', 'skip-normarc',
|
|
'skip-webinstall', 'skip-pbuilder',
|
|
'skip-rnotes', 'skip-stats',
|
|
|
|
# Source description options
|
|
'version=s', 'autoversion|a',
|
|
'kohaclone|k=s',
|
|
'branch=s', 'since=s',
|
|
'distribution=s',
|
|
|
|
# Execution options
|
|
'database=s',
|
|
'user=s', 'password=s',
|
|
'use-dist-rnotes',
|
|
'maintainer-name=s', 'maintainer-email=s',
|
|
'post-deploy-script=s@',
|
|
|
|
# Output options
|
|
'build-result|b=s', 'errorlog=s',
|
|
'tarball|t=s', 'rnotes|r=s',
|
|
'stats=s', 'alert=s',
|
|
|
|
# Announcement options
|
|
'email-file=s',
|
|
'email-recipients=s', 'email-subject=s',
|
|
'email-template=s',
|
|
|
|
# Bugzilla options
|
|
'bzlogin:s','bzpassword:s',
|
|
);
|
|
|
|
binmode( STDOUT, ":utf8" );
|
|
|
|
if ( $cmdline{help} ) {
|
|
usage();
|
|
}
|
|
|
|
if ( defined( $cmdline{config} ) && -f File::Spec->rel2abs( $cmdline{config} ) )
|
|
{
|
|
$config->read( $cmdline{config} );
|
|
}
|
|
foreach my $key ( keys %defaults ) {
|
|
$config->param( $key, $defaults{$key} ) unless $config->param($key);
|
|
}
|
|
foreach my $key ( keys %cmdline ) {
|
|
$config->param( $key, $cmdline{$key} );
|
|
}
|
|
|
|
my $starttime = time();
|
|
|
|
chdir $config->param('kohaclone')
|
|
if ( $config->param('kohaclone') && -d $config->param('kohaclone') );
|
|
|
|
my $reltools = File::Spec->rel2abs( dirname(__FILE__) );
|
|
|
|
$config->param( 'kohaclone', File::Spec->rel2abs( File::Spec->curdir() ) )
|
|
unless ( $config->param('kohaclone') && -d $config->param('kohaclone') );
|
|
|
|
$ENV{PERL5LIB} = $config->param('kohaclone');
|
|
my @marcflavours;
|
|
push @marcflavours, 'MARC21' unless $config->param('skip-marc21');
|
|
push @marcflavours, 'UNIMARC' unless $config->param('skip-unimarc');
|
|
push @marcflavours, 'NORMARC' unless $config->param('skip-normarc');
|
|
|
|
set_default( 'branch', `git branch | grep '*' | sed -e 's/^* //'` );
|
|
my $dist = $config->param('branch');
|
|
$dist =~ s#[/_]#-#g;
|
|
set_default( 'distribution', $dist );
|
|
|
|
my $kohaversion = `grep 'VERSION = ' kohaversion.pl | sed -e "s/^[^']*'//" -e "s/';//"`;
|
|
set_default( 'version', $kohaversion );
|
|
|
|
set_default( 'maintainer-name', `git config --global --get user.name` );
|
|
|
|
set_default( 'maintainer-email', `git config --global --get user.email` );
|
|
|
|
set_default( 'build-result',
|
|
"$ENV{HOME}/releases/"
|
|
. $config->param('distribution') . '/'
|
|
. $config->param('version') );
|
|
|
|
make_path( $config->param('build-result') );
|
|
|
|
opendir( DIR, $config->param('build-result') );
|
|
|
|
while ( my $file = readdir(DIR) ) {
|
|
|
|
# We only want files
|
|
$file = $config->param('build-result') . "$file";
|
|
next unless ( -f $file );
|
|
|
|
unlink $file;
|
|
}
|
|
|
|
$ENV{TEST_QA} = 1;
|
|
|
|
if ( $config->param('tarball') =~ m#/# ) {
|
|
$config->param( 'tarball', '' )
|
|
unless ( -d dirname( $config->param('tarball') ) );
|
|
}
|
|
elsif ( $config->param('tarball') ) {
|
|
$config->param( 'tarball', build_result( $config->param('tarball') ) );
|
|
}
|
|
|
|
set_default( 'email-file', build_result('announcement.eml') );
|
|
|
|
set_default(
|
|
'tarball',
|
|
build_result(
|
|
'koha-'
|
|
. $config->param('distribution') . '-'
|
|
. $config->param('version')
|
|
. '.tar.gz'
|
|
)
|
|
);
|
|
|
|
set_default( 'rnotes', build_result('release_notes.txt') );
|
|
|
|
set_default( 'stats', build_result('statistics') );
|
|
|
|
my $lasttag = `git describe --abbrev=0`;
|
|
chomp $lasttag;
|
|
|
|
set_default( 'since', $lasttag );
|
|
|
|
set_default( 'errorlog', build_result('errors.log') );
|
|
|
|
unlink $config->param('tarball');
|
|
unlink $config->param('rnotes') unless $config->param('use-dist-rnotes');
|
|
unlink $config->param('errorlog');
|
|
|
|
print_log(
|
|
colored(
|
|
"Starting release test at "
|
|
. strftime( '%D %T', localtime($starttime) ),
|
|
'blue'
|
|
)
|
|
);
|
|
print_log( "\tBranch: "
|
|
. $config->param('branch')
|
|
. "\n\tDistribution: "
|
|
. $config->param('distribution')
|
|
. "\n\tVersion: "
|
|
. $config->param('version')
|
|
. "\n" );
|
|
|
|
unless ( index $config->param('version'), $kohaversion ) {
|
|
$version_mismatch = 1;
|
|
}
|
|
|
|
unless ( $config->param('skip-tests') ) {
|
|
tap_task(
|
|
"Running unit tests",
|
|
0,
|
|
undef,
|
|
tap_dir( $config->param('kohaclone') . '/t' ),
|
|
tap_dir( $config->param('kohaclone') . '/t/db_dependent' ),
|
|
tap_dir( $config->param('kohaclone') . '/t/db_dependent/Labels' ),
|
|
$config->param('kohaclone') . '/xt/author/icondirectories.t',
|
|
$config->param('kohaclone') . '/xt/author/podcorrectness.t',
|
|
$config->param('kohaclone') . '/xt/author/translatable-templates.t',
|
|
$config->param('kohaclone') . '/xt/author/valid-templates.t',
|
|
$config->param('kohaclone') . '/xt/permissions.t',
|
|
$config->param('kohaclone') . '/xt/single_quotes.t',
|
|
$config->param('kohaclone') . '/xt/tt_valid.t'
|
|
);
|
|
$finished_tests = 'yes';
|
|
}
|
|
|
|
unless ( $config->param('skip-deb') ) {
|
|
unless ( $config->param('skip-pbuilder') ) {
|
|
print_log("Updating pbuilder...");
|
|
run_cmd("sudo pbuilder update --keyring '$reltools/debian.koha-community.org.gpg' 2>&1");
|
|
warn colored( "Error updating pbuilder. Continuing anyway.",
|
|
'bold red' )
|
|
if ($?);
|
|
}
|
|
|
|
$ENV{DEBEMAIL} = $config->param('maintainer-email');
|
|
$ENV{DEBFULLNAME} = $config->param('maintainer-name');
|
|
|
|
my $extra_args = '';
|
|
$extra_args = ' --noautoversion' unless ( $config->param('autoversion') );
|
|
shell_task(
|
|
"Building packages",
|
|
"debian/build-git-snapshot --distribution="
|
|
. $config->param('distribution') . " -r "
|
|
. $config->param('build-result') . " -v "
|
|
. $config->param('version')
|
|
. "$extra_args 2>&1"
|
|
);
|
|
|
|
fail('Building package')
|
|
unless $output =~
|
|
m#^dpkg-deb: building package `koha-common' in `[^'`/]*/([^']*)'.$#m;
|
|
$config->param( 'package', build_result($1) );
|
|
|
|
fail('Building package') unless ( -f $config->param('package') );
|
|
|
|
$built_packages = 'yes';
|
|
}
|
|
|
|
unless ( $config->param('skip-tgz') ) {
|
|
print_log("Preparing release tarball...");
|
|
|
|
shell_task(
|
|
"Creating archive",
|
|
'git archive --format=tar --prefix=koha-'
|
|
. $config->param('version') . '/ '
|
|
. $config->param('branch')
|
|
. ' | gzip > '
|
|
. $config->param('tarball'),
|
|
1
|
|
);
|
|
|
|
$built_tarball = 'yes';
|
|
|
|
shell_task( "Signing archive", "gpg -sb " . $config->param('tarball'), 1 )
|
|
if ( $config->param('sign') );
|
|
|
|
shell_task(
|
|
"md5summing archive",
|
|
"md5sum "
|
|
. $config->param('tarball') . " > "
|
|
. $config->param('tarball') . ".MD5",
|
|
1
|
|
);
|
|
|
|
if ( $config->param('sign') ) {
|
|
shell_task( "Signing md5sum",
|
|
"gpg --clearsign " . $config->param('tarball') . ".MD5", 1 );
|
|
$signed_tarball = 'yes';
|
|
}
|
|
|
|
if ( $config->param('deploy') ) {
|
|
$config->param('staging', build_result('staging'));
|
|
mkdir $config->param('staging');
|
|
symlink $config->param('tarball'), $config->param('staging') . '/' . basename($config->param('tarball'));
|
|
symlink $config->param('tarball') . '.MD5', $config->param('staging') . '/' . basename($config->param('tarball') . '.MD5');
|
|
if ($signed_tarball) {
|
|
symlink $config->param('tarball') . '.MD5.asc', $config->param('staging') .'/' . basename($config->param('tarball') . '.MD5.asc');
|
|
symlink $config->param('tarball') . '.sig', $config->param('staging') .'/' . basename($config->param('tarball') . '.sig');
|
|
}
|
|
}
|
|
}
|
|
|
|
unless ( $config->param('skip-rnotes') || $config->param('use-dist-rnotes') ) {
|
|
print_log("Generating release notes...");
|
|
run_cmd(
|
|
"$reltools/get_bugs.pl -r "
|
|
. $config->param('rnotes') . " -t "
|
|
. $config->param('since') . " -v "
|
|
. $config->param('version')
|
|
.($config->param('bzlogin')?" -u ".$config->param('bzlogin')." -p ".$config->param('bzpassword'):"")
|
|
. " --verbose 2>&1"
|
|
);
|
|
warn colored( "Error generating release notes. Continuing anyway.",
|
|
'bold red' ) if ($?);
|
|
}
|
|
|
|
system('which gitdm 2>&1 > /dev/null');
|
|
$config->param('skip-stats', 1) if $?;
|
|
unless ( $config->param('skip-stats') ) {
|
|
shell_task(
|
|
"Generating statistics",
|
|
"git log -p -M " . $config->param('since')
|
|
. "..HEAD | gitdm -b $reltools -c $reltools/gitdm.config -u -s -a -o "
|
|
. $config->param('stats') . ".txt -h "
|
|
. $config->param('stats') . ".html 2>&1"
|
|
);
|
|
}
|
|
|
|
unless ( $config->param('skip-deb') || $config->param('skip-install') ) {
|
|
for my $flavour (@marcflavours) {
|
|
my $lflavour = lc $flavour;
|
|
|
|
print_log("Installing from package for $flavour...");
|
|
my ($lxc_ip, $ssh) = create_lxc();
|
|
ssh_task( $ssh, "Downloading package...", "wget -nv http://10.0.3.1" . $config->param('package') . ' 2>&1', '', 1 );
|
|
ssh_task( $ssh, "Installing package...", "sudo dpkg --no-debsig -i " . basename($config->param('package')) . ' 2>&1; sudo apt-get update; sudo apt-get -y -f --force-yes install 2>&1', '', 1 );
|
|
ssh_task( $ssh, "Running koha-create for $flavour",
|
|
"sudo koha-create --marcflavor=$lflavour --create-db pkgrel 2>&1", '',
|
|
1 );
|
|
|
|
print_log("Unable to run webinstaller for $flavour package due to Koha breakage") if (! $config->param('skip-webinstall'));
|
|
unless ( $config->param('skip-webinstall') || 1 ) {
|
|
my $pkg_user = $ssh->capture("sudo xmlstarlet sel -t -v 'yazgfs/config/user' '/etc/koha/sites/pkgrel/koha-conf.xml'");
|
|
my $pkg_pass = $ssh->capture("sudo xmlstarlet sel -t -v 'yazgfs/config/pass' '/etc/koha/sites/pkgrel/koha-conf.xml'");
|
|
chomp $pkg_user;
|
|
chomp $pkg_pass;
|
|
my $harness_args = {
|
|
test_args => [
|
|
"http://$lxc_ip:8080", "http://$lxc_ip",
|
|
"$flavour", "$pkg_user",
|
|
"$pkg_pass"
|
|
]
|
|
};
|
|
tap_task( "Running webinstaller for $flavour",
|
|
1, $harness_args, "$reltools/install-fresh.pl" );
|
|
|
|
push @tested_package_installs, $flavour;
|
|
}
|
|
clean_lxc();
|
|
$lxc_host = '';
|
|
}
|
|
}
|
|
|
|
if ( $config->param('sign') && !$config->param('skip-deb') ) {
|
|
shell_task( "Signing packages", "debsign " . build_result('*.changes') );
|
|
$signed_packages = 'yes';
|
|
}
|
|
|
|
if ( $config->param('deploy') && !$config->param('skip-deb') ) {
|
|
shell_task(
|
|
"Importing packages to apt repo",
|
|
"dput koha " . build_result('*.changes')
|
|
);
|
|
$deployed = 'yes';
|
|
}
|
|
|
|
unless ( $config->param('skip-tgz') || $config->param('skip-install') ) {
|
|
for my $flavour (@marcflavours) {
|
|
my $lflavour = lc $flavour;
|
|
print_log("Installing from tarball for $flavour...");
|
|
|
|
my ($lxc_ip, $ssh) = create_lxc();
|
|
my $subdir = 'koha-' . $config->param('version');
|
|
ssh_task( $ssh, "Downloading tarball...", "wget -nv http://10.0.3.1" . $config->param('tarball') . ' 2>&1', '', 1 );
|
|
ssh_task( $ssh, "Untarring tarball...", "tar zxvf " . basename($config->param('tarball')) . ' 2>&1', '', 1 );
|
|
ssh_task( $ssh, "Installing dependencies...", "sudo apt-get -y --force-yes install `cat install_misc/ubuntu.12.04.packages | grep install | sed -e 's/install\$//' | tr -d ' \\t' | tr '\\n' ' '` 2>&1", $subdir, 1 );
|
|
my $env_vars = "DB_HOST=localhost DB_NAME=" . $config->param('database') . " DB_USER=" . $config->param('user') . " DB_PASS=" . $config->param('password') . " ZEBRA_MARC_FORMAT=$lflavour PERL_MM_USE_DEFAULT=1";
|
|
ssh_task( $ssh, "Running perl Makefile.PL for $flavour",
|
|
"$env_vars perl Makefile.PL 2>&1", $subdir, 1 );
|
|
ssh_task( $ssh, "Running make for $flavour...", "make 2>&1", $subdir, 1 );
|
|
|
|
ssh_task( $ssh, "Rewriting Apache config for $flavour",
|
|
"sed -i -e 's/<VirtualHost 127.0.1.1:80>/<VirtualHost *:80>/' -e 's/<VirtualHost 127.0.1.1:8080>/<VirtualHost *:8080>/' blib/KOHA_CONF_DIR/koha-httpd.conf",
|
|
$subdir, 1 );
|
|
|
|
ssh_task( $ssh, "Running make test for $flavour...", "make test 2>&1", $subdir, 1 );
|
|
ssh_task( $ssh, "Running make install for $flavour...",
|
|
"sudo make install 2>&1", $subdir, 1 );
|
|
ssh_task( $ssh, "Linking and loading Apache site for $flavour...", "sudo ln -s /etc/koha/koha-httpd.conf /etc/apache2/sites-available/koha && sudo a2ensite koha && sudo apache2ctl restart", '', 1 );
|
|
|
|
print_log("Unable to run webinstaller for $flavour tarball due to Koha breakage") if (! $config->param('skip-webinstall'));
|
|
unless ( $config->param('skip-webinstall') || 1 ) {
|
|
my $harness_args = {
|
|
test_args => [
|
|
"http://$lxc_ip:8080", "http://$lxc_ip",
|
|
"$flavour", $config->param('user'),
|
|
$config->param('password')
|
|
]
|
|
};
|
|
tap_task( "Running webinstaller for $flavour",
|
|
1, $harness_args, "$reltools/install-fresh.pl" );
|
|
|
|
push @tested_tarball_installs, $flavour;
|
|
}
|
|
clean_lxc();
|
|
$lxc_host = '';
|
|
}
|
|
}
|
|
|
|
if ( $config->param('tag') ) {
|
|
my $tag_action = $config->param('sign') ? '-s' : '-a';
|
|
shell_task(
|
|
"Tagging current commit",
|
|
"git tag $tag_action -m 'Koha release "
|
|
. $config->param('version') . "' v"
|
|
. $config->param('version') . " 2>&1"
|
|
);
|
|
$tagged = 'yes';
|
|
}
|
|
|
|
generate_email() unless $config->param('skip-rnotes');
|
|
|
|
if ( defined $config->param('push') ) {
|
|
foreach my $target ($config->param('push')) {
|
|
if ( $target =~ m/^([^:]*):(.*)$/ ) {
|
|
shell_task( "Pushing to branch $2 on remote $1",
|
|
"git push $1 " . $config->param('branch') . ":$2" );
|
|
if ( $config->param('tag') ) {
|
|
shell_task( "Pushing tag to remote $1",
|
|
"git push $1 v" . $config->param('version') );
|
|
}
|
|
} else {
|
|
shell_task( "Pushing to default remote/branch",
|
|
"git push" );
|
|
if ( $config->param('tag') ) {
|
|
shell_task( "Pushing tag to default remote",
|
|
"git push v" . $config->param('version') );
|
|
}
|
|
}
|
|
}
|
|
$pushed = 'yes';
|
|
}
|
|
|
|
my $configfile = build_result('summary.cfg');
|
|
$config->write($configfile);
|
|
|
|
if ( $config->param('deploy') && $config->param('post-deploy-script') ) {
|
|
foreach my $script ($config->param('post-deploy-script')) {
|
|
shell_task( "Running post-deploy script",
|
|
$script . ' ' . $configfile );
|
|
}
|
|
}
|
|
|
|
if ( $config->param('clean') ) {
|
|
clean_lxc();
|
|
remove_tree( $config->param('build-result') );
|
|
$cleaned = 'yes';
|
|
}
|
|
|
|
success();
|
|
|
|
sub build_result {
|
|
my @components = @_;
|
|
my $path = $config->param('build-result');
|
|
|
|
foreach my $component (@components) {
|
|
$path .= "/$component";
|
|
}
|
|
return $path;
|
|
}
|
|
|
|
sub set_default {
|
|
my $key = shift;
|
|
my $value = shift;
|
|
|
|
chomp($value);
|
|
$config->param( $key, $value ) unless $config->param($key);
|
|
}
|
|
|
|
sub clean_lxc {
|
|
shell_task( "Shutting down lxc container...", "sudo lxc-stop -n $lxc_host", 1 ) if $lxc_host;
|
|
}
|
|
|
|
sub summary {
|
|
my ($capsule_summary) = @_;
|
|
my $endtime = time();
|
|
my $totaltime = ceil( ( $endtime - $starttime ) * 1000 );
|
|
$starttime = strftime( '%D %T', localtime($starttime) );
|
|
$endtime = strftime( '%D %T', localtime($endtime) );
|
|
my $skipped = '';
|
|
my $tested_tarball_install = 'no';
|
|
my $tested_package_install = 'no';
|
|
$tested_tarball_install = join( ', ', @tested_tarball_installs )
|
|
if ( scalar(@tested_tarball_installs) );
|
|
$tested_package_install = join( ', ', @tested_package_installs )
|
|
if ( scalar(@tested_package_installs) );
|
|
|
|
my %vars = $config->vars();
|
|
foreach my $key ( sort keys %vars ) {
|
|
if ( $key =~ m/^skip-([a-z]+)$/ ) {
|
|
$skipped .= ", $1" if ( $config->param($key) );
|
|
}
|
|
}
|
|
$skipped =~ s/^, //;
|
|
$skipped = 'none' unless $skipped;
|
|
$config->param( 'package', 'none' )
|
|
unless ( -s $config->param('package') && not $config->param('skip-deb') );
|
|
$config->param( 'tarball', 'none' )
|
|
unless ( -s $config->param('tarball') && not $config->param('skip-tgz') );
|
|
$config->param( 'rnotes', 'none' )
|
|
unless ( -s $config->param('rnotes')
|
|
&& not $config->param('skip-rnotes') );
|
|
return if $config->param('quiet') > 1;
|
|
my $branch = $config->param('branch');
|
|
my $distribution = $config->param('distribution');
|
|
my $version = $config->param('version');
|
|
my $maintainer =
|
|
$config->param('maintainer-name') . ' <'
|
|
. $config->param('maintainer-email') . '>';
|
|
my $tarball = $config->param('tarball');
|
|
my $package = $config->param('package');
|
|
my $rnotes = $config->param('rnotes');
|
|
my $stats = $config->param('stats');
|
|
my $emailfile = $config->param('email-file');
|
|
my $logfile = build_result('release-tool.log');
|
|
my $summary = <<_SUMMARY_;
|
|
$capsule_summary
|
|
|
|
Release test report
|
|
=======================================================
|
|
Branch: $branch
|
|
Distribution: $distribution
|
|
Version: $version
|
|
Maintainer: $maintainer
|
|
Run started at: $starttime
|
|
Run ended at: $endtime
|
|
Total run time: $totaltime ms
|
|
Skipped: $skipped
|
|
Finished tests: $finished_tests
|
|
Built packages: $built_packages
|
|
Tested package install: $tested_package_install
|
|
Deployed packages: $deployed
|
|
Signed packages: $signed_packages
|
|
Built tarball: $built_tarball
|
|
Tested tarball install: $tested_tarball_install
|
|
Signed tarball: $signed_tarball
|
|
Tagged git repository: $tagged
|
|
Pushed git branch: $pushed
|
|
Cleaned: $cleaned
|
|
Tarball file: $tarball
|
|
Package file: $package
|
|
Release notes: $rnotes
|
|
Statistics: $stats\[.txt/.html\]
|
|
E-mail file: $emailfile
|
|
Summary config file: $configfile
|
|
Log file: $logfile
|
|
_SUMMARY_
|
|
|
|
$summary .= colored( "Version mismatch between requested version " .
|
|
$config->param('version') . " and Koha version $kohaversion!", 'red' ) . "\n"
|
|
if $version_mismatch;
|
|
$summary .= $capsule_summary;
|
|
|
|
print $summary unless $config->param('quiet') > 1;
|
|
|
|
$log .= "\n$summary";
|
|
|
|
open my $logfh, '>', $logfile;
|
|
print $logfh $log;
|
|
close($logfh);
|
|
|
|
|
|
if ( $config->param('alert') ) {
|
|
$summary =~ s/\x1b\[[0-9]*m//g;
|
|
$summary .= "=======================================================\n\n";
|
|
my $msg = MIME::Lite->new(
|
|
From => $config->param('maintainer-email'),
|
|
To => $config->param('alert'),
|
|
Subject => 'Release tool completed',
|
|
Data => $summary,
|
|
Type => 'multipart/mixed'
|
|
);
|
|
$msg->attach(
|
|
Type => 'text/plain',
|
|
Data => $summary
|
|
);
|
|
$log =~ s/\x1b\[[0-9]*m//g;
|
|
$msg->attach(
|
|
Type => 'text/plain',
|
|
Data => $log,
|
|
Filename => 'release-tool.log'
|
|
);
|
|
$msg->send;
|
|
}
|
|
}
|
|
|
|
sub success {
|
|
summary(colored( "Successfully finished release test", 'green' ) . "\n");
|
|
|
|
exit 0;
|
|
}
|
|
|
|
sub fail {
|
|
my $component = shift;
|
|
my $callback = shift;
|
|
|
|
summary(colored( $component, 'bold red' ) . ' ' .
|
|
colored( " failed in release test", 'red' ) . "\n");
|
|
|
|
if ($output) {
|
|
open( my $errorlog, ">", $config->param('errorlog') )
|
|
or die "Unable to open error log for writing";
|
|
print $errorlog $output;
|
|
close($errorlog);
|
|
}
|
|
|
|
print colored( "Error report at " . $config->param('errorlog'), 'red' ),
|
|
"\n"
|
|
unless $config->param('quiet') > 1;
|
|
|
|
$callback->() if ( ref $callback eq 'CODE' );
|
|
|
|
exit 1;
|
|
}
|
|
|
|
sub print_log {
|
|
$log .= join(' ', @_) . "\n";
|
|
print @_, "\n" unless $config->param('quiet');
|
|
}
|
|
|
|
sub tap_dir {
|
|
my $directory = shift;
|
|
my @tests;
|
|
|
|
opendir( DIR, $directory );
|
|
|
|
while ( my $file = readdir(DIR) ) {
|
|
|
|
# We only want files
|
|
next unless ( -f "$directory/$file" );
|
|
|
|
# Use a regular expression to find files ending in .t
|
|
next unless ( $file =~ m/\.t$/ );
|
|
push @tests, "$directory/$file";
|
|
}
|
|
return sort @tests;
|
|
}
|
|
|
|
sub create_lxc {
|
|
# Sorry, creating the lxc container sucks. Deal with it.
|
|
print_log(" Creating lxc container...");
|
|
my $command = "sudo lxc-start-ephemeral -d -o ubuntu";
|
|
$log .= "> $command\n";
|
|
print colored( "> $command\n", 'cyan' )
|
|
if ( $config->param('verbose') >= 1 );
|
|
my $pid = open( my $outputfh, "-|", "$command" )
|
|
or die "Unable to run $command\n";
|
|
while (<$outputfh>) {
|
|
print $_ if ( $config->param('verbose') >= 2 );
|
|
$output .= $_;
|
|
if ($_ =~ /^([^ ]*) is running/) {
|
|
$lxc_host = $1;
|
|
last;
|
|
}
|
|
}
|
|
close($outputfh);
|
|
|
|
my $lxc_ip = `lxc-ip -n $lxc_host`;
|
|
chomp($lxc_ip);
|
|
print_log(" Connecting to lxc container...");
|
|
my $ssh = Net::OpenSSH->new("ubuntu\@$lxc_ip", master_opts => [-o => "StrictHostKeyChecking=no"]);
|
|
|
|
return ($lxc_ip, $ssh);
|
|
}
|
|
|
|
sub run_cmd {
|
|
my $command = shift;
|
|
$log .= "> $command\n";
|
|
print colored( "> $command\n", 'cyan' )
|
|
if ( $config->param('verbose') >= 1 );
|
|
my $pid = open( my $outputfh, "-|", "$command" )
|
|
or die "Unable to run $command\n";
|
|
while (<$outputfh>) {
|
|
print $_ if ( $config->param('verbose') >= 2 );
|
|
$output .= $_;
|
|
}
|
|
close($outputfh);
|
|
$log .= $output . "\n";
|
|
}
|
|
|
|
sub shell_task {
|
|
my $message = shift;
|
|
my $command = shift;
|
|
my $callback;
|
|
$callback = pop @_ if ( $#_ && ref $_[$#_] eq 'CODE' );
|
|
my $subtask = shift || 0;
|
|
my $logmsg = ( ' ' x $subtask ) . $message;
|
|
print_log("$logmsg...");
|
|
$output = '';
|
|
run_cmd($command);
|
|
fail( $message, $callback ) if ($?);
|
|
}
|
|
|
|
sub ssh_task {
|
|
my $ssh = shift;
|
|
my $message = shift;
|
|
my $command = shift;
|
|
my $dir = shift;
|
|
my $callback;
|
|
$callback = pop @_ if ( $#_ && ref $_[$#_] eq 'CODE' );
|
|
my $subtask = shift || 0;
|
|
my $logmsg = ( ' ' x $subtask ) . $message;
|
|
print_log("$logmsg...");
|
|
$command = "cd $dir && $command" if $dir;
|
|
$log .= "> $command\n";
|
|
print colored( "> $command\n", 'cyan' )
|
|
if ( $config->param('verbose') >= 1 );
|
|
$output = '';
|
|
my ($outputfh, $pid) = $ssh->pipe_out($command);
|
|
die "Unable to run $command\n" unless $pid;
|
|
while (<$outputfh>) {
|
|
print $_ if ( $config->param('verbose') >= 2 );
|
|
$output .= $_;
|
|
}
|
|
close($outputfh);
|
|
$log .= $output . "\n";
|
|
fail( $message, $callback ) if ($ssh->error);
|
|
}
|
|
|
|
sub tap_task {
|
|
my $message = shift;
|
|
my $subtask = shift;
|
|
my $harness_args = shift;
|
|
my $callback;
|
|
$callback = pop @_ if ( ref $_[$#_] eq 'CODE' );
|
|
my @tests = @_;
|
|
my $logmsg = ( ' ' x $subtask ) . $message;
|
|
|
|
if ( $config->param('verbose') ) {
|
|
$harness_args->{'verbosity'} = 1;
|
|
}
|
|
elsif ( $config->param('quiet') ) {
|
|
$harness_args->{'verbosity'} = -1;
|
|
}
|
|
$harness_args->{'lib'} = [ $config->param('kohaclone') ];
|
|
$harness_args->{'merge'} = 1;
|
|
|
|
print_log("$logmsg...");
|
|
|
|
if ( $config->param('verbose') >= 1 ) {
|
|
my $command = 'prove ';
|
|
foreach my $test (@tests) {
|
|
$command .= "$test ";
|
|
}
|
|
print colored( "> $command\n", 'cyan' );
|
|
$log .= "> $command\n";
|
|
}
|
|
|
|
my $pid = open( my $testfh, '-|' ) // die "Can't fork to run tests: $!\n";
|
|
if ($pid) {
|
|
while (<$testfh>) {
|
|
print $_ if ( $config->param('verbose') >= 2 );
|
|
$output .= $_;
|
|
}
|
|
$log .= $output . "\n";
|
|
waitpid( $pid, 0 );
|
|
fail( $message, $callback ) if ($?);
|
|
close($testfh);
|
|
}
|
|
else {
|
|
my $harness = TAP::Harness->new($harness_args);
|
|
my $aggregator = $harness->runtests(@tests);
|
|
exit 1 if $aggregator->failed;
|
|
exit 0;
|
|
}
|
|
}
|
|
|
|
sub process_tt_task {
|
|
my $message = shift;
|
|
my $subtask = shift;
|
|
my $template = shift;
|
|
my $args = shift;
|
|
my $logmsg = ( ' ' x $subtask ) . $message;
|
|
|
|
print_log("$logmsg...");
|
|
|
|
my $tt = Template->new(
|
|
{
|
|
INCLUDE_PATH => "$reltools",
|
|
ABSOLUTE => 1,
|
|
}
|
|
);
|
|
my $output;
|
|
$tt->process( $template, $args, \$output ) || fail($message);
|
|
return $output;
|
|
}
|
|
|
|
sub generate_email {
|
|
my $content = process_tt_task(
|
|
'Generating e-mail',
|
|
0,
|
|
$config->param('email-template'),
|
|
{
|
|
VERSION => $config->param('version'),
|
|
RELNOTES => $config->param('rnotes')
|
|
}
|
|
);
|
|
$content =~ s/^####.*$//m;
|
|
my $msg = MIME::Lite->new(
|
|
From => $config->param('maintainer-email'),
|
|
To => $config->param('email-recipients'),
|
|
Subject => $config->param('email-subject'),
|
|
Data => $content,
|
|
);
|
|
open( my $emailfh, ">", $config->param('email-file') );
|
|
$msg->print($emailfh);
|
|
close($emailfh);
|
|
}
|
|
|
|
sub interrupt {
|
|
undef $SIG{INT};
|
|
warn "**** YOU INTERRUPTED THE SCRIPT ****\n";
|
|
summary();
|
|
die "**** YOU INTERRUPTED THE SCRIPT ****\n";
|
|
}
|
|
|
|
=head1 DPUT CONFIGURATION
|
|
|
|
In order for the deploy step to work, you will need to have a signed apt repository
|
|
set up. Once you have set up your repo, put the following in your ~/.dput.cf:
|
|
|
|
[koha]
|
|
method = local
|
|
incoming = /home/apt/koha/incoming
|
|
run_dinstall = 0
|
|
post_upload_command = reprepro -b /home/apt/koha processincoming default
|
|
|
|
=head1 PBUILDER CONFIGURATION
|
|
|
|
Koha's C<build-git-snapshot> script uses uses L<pbuilder(8)> for building the
|
|
Debian packages, so you will need a working pbuilder environment. To create
|
|
one, you can use the following command:
|
|
|
|
sudo pbuilder create \
|
|
--othermirror 'deb http://debian.koha-community.org/koha squeeze main' \
|
|
--mirror http://ftp.debian.org/debian \
|
|
--keyring '${release-tools-repo}/debian.koha-community.org.gpg'
|
|
|
|
=head1 LXC CONFIGURATION
|
|
|
|
Testing the Koha installation process requires an LXC container called "ubuntu"
|
|
configured with the following features:
|
|
|
|
=over 8
|
|
|
|
=item Ubuntu Precise chosen as the distro
|
|
|
|
=item The following line in the sudoers file:
|
|
|
|
ubuntu ALL=NOPASSWD: /usr/bin/make, /usr/bin/apt-get, /sbin/poweroff, \
|
|
/bin/ln, /usr/sbin/apache2ctl, /usr/sbin/a2ensite, /usr/bin/dpkg, \
|
|
/usr/sbin/koha-create, /usr/bin/xmlstarlet
|
|
|
|
=item The current user's SSH key must be in /home/ubuntu/.ssh/authorized_keys2
|
|
in the container.
|
|
|
|
=item mysql-server must be installed and a database and user created for the
|
|
tarball installation.
|
|
|
|
=item You must make your build result directory available via HTTP on the
|
|
lxcbr interface. A configuration similar to the one nginx configuration below
|
|
is recommended:
|
|
|
|
server {
|
|
listen 10.0.3.1:80;
|
|
server_name 10.0.3.1;
|
|
root /var/www;
|
|
|
|
location /home/jcamins/releases {
|
|
alias /home/jcamins/releases;
|
|
}
|
|
}
|
|
|
|
=item An apt cache set up with apt-cacher-ng is recommended to speed up the
|
|
process.
|
|
|
|
=back
|
|
|
|
=head1 SEE ALSO
|
|
|
|
L<dch(1)>, L<dput(1)>, L<lxc(7)>, L<pbuilder(8)>, L<reprepro(1)>
|
|
|
|
=head1 AUTHOR
|
|
|
|
Jared Camins-Esakov <jcamins@cpbibliography.com>
|
|
|
|
=cut
|
|
|