mirror of
https://git.lapiole.org/dani/ansible-roles.git
synced 2025-04-10 15:23:27 +02:00
358 lines
11 KiB
Perl
358 lines
11 KiB
Perl
#!/usr/bin/perl
|
|
|
|
use strict;
|
|
use warnings;
|
|
use Linux::Inotify2;
|
|
use YAML::Tiny;
|
|
use Getopt::Long;
|
|
use File::stat;
|
|
use File::Find;
|
|
use File::Basename;
|
|
use File::Path qw(make_path);
|
|
use File::Copy qw(move);
|
|
use AnyEvent;
|
|
use RPM2;
|
|
use Time::HiRes 'time';
|
|
use Email::MIME;
|
|
use Email::Sender::Simple qw(sendmail);
|
|
use Email::Sender::Transport::Sendmail;
|
|
use Net::LDAP;
|
|
|
|
# Init an empty conf
|
|
my $conf = {};
|
|
|
|
# Disable output buffering
|
|
$| = 1;
|
|
|
|
# Defaults for command line flags
|
|
my $opt = {
|
|
config => '../etc/config.yml',
|
|
verbose => 0,
|
|
quiet => 0
|
|
};
|
|
|
|
# Read some options from the command line
|
|
GetOptions (
|
|
'config=s' => \$opt->{config},
|
|
'quiet' => \$opt->{quiet},
|
|
'verbose' => \$opt->{verbose}
|
|
);
|
|
|
|
# Check if the config file exists, and if so, parse it
|
|
# and load it in $conf
|
|
if ( -e $opt->{config} ) {
|
|
log_verbose( "Reading config file " . $opt->{config} );
|
|
my $yaml = YAML::Tiny->read( $opt->{config} );
|
|
|
|
if ( not $yaml or not $yaml->[0] ) {
|
|
die "Config file " . $opt->{config} . " is invalid\n";
|
|
}
|
|
$conf = $yaml->[0];
|
|
} else {
|
|
# If the config file doesn't exist, just die
|
|
die "Config file " . $opt->{config} . " doesn't exist\n";
|
|
}
|
|
|
|
# If ldap is configured, we'll use it to lookup email
|
|
# addresses of submitters to send them notifications
|
|
my $ldap;
|
|
my $ldap_msg;
|
|
if (defined $conf->{ldap} and defined $conf->{ldap}->{servers}){
|
|
log_verbose("Connecting to " . join(', ', @{$conf->{ldap}->{servers}}));
|
|
$ldap = new Net::LDAP($conf->{ldap}->{servers},
|
|
timeout => 10,
|
|
);
|
|
if (not defined $ldap){
|
|
log_info("Couldn't connect to any LDAP servers (" . join(',', @{$conf->{ldap}->{servers}}) . ")");
|
|
} else {
|
|
if (defined $conf->{ldap}->{start_tls} and $conf->{ldap}->{start_tls}){
|
|
log_verbose("Upgrade LDAP connection using StartTLS");
|
|
$ldap_msg = $ldap->start_tls(
|
|
verify => 'require'
|
|
);
|
|
if ($ldap_msg->code){
|
|
log_verbose("StartTLS failed : " . $ldap_msg->error);
|
|
log_verbose("LDAP support will be disabled");
|
|
$ldap = undef;
|
|
}
|
|
}
|
|
if (defined $conf->{ldap}->{bind_dn} and defined $conf->{ldap}->{bind_pass}){
|
|
log_verbose("Binding as $conf->{ldap}->{bind_dn}");
|
|
$ldap_msg = $ldap->bind(
|
|
$conf->{ldap}->{bind_dn},
|
|
password => $conf->{ldap}->{bind_pass}
|
|
);
|
|
if ($ldap_msg->code){
|
|
log_verbose("LDAP bind failed : " . $ldap_msg->error);
|
|
log_verbose("LDAP support will be disabled");
|
|
$ldap = undef;
|
|
}
|
|
} else {
|
|
log_verbose("Using anonymous bind");
|
|
$ldap_msg = $ldap->bind;
|
|
}
|
|
}
|
|
} else {
|
|
log_verbose("No LDAP servers configured");
|
|
}
|
|
|
|
my $inotify = new Linux::Inotify2
|
|
or die "Unable to create new inotify object: $!";
|
|
|
|
log_verbose("Searching for folders in $conf->{paths}->{uploads}");
|
|
find({
|
|
wanted => sub { -d and create_watcher($inotify, $File::Find::name); }
|
|
}, $conf->{paths}->{uploads});
|
|
|
|
my $cv = AnyEvent->condvar;
|
|
|
|
my $poller = AnyEvent->io(
|
|
fh => $inotify->fileno,
|
|
poll => 'r',
|
|
cb => sub { $inotify->poll }
|
|
);
|
|
|
|
# Receive event signals (inotify signals)
|
|
$cv->recv;
|
|
|
|
# Print messages only if the verbose flag was given
|
|
sub log_verbose {
|
|
my $msg = shift;
|
|
print $msg . "\n" if ( $opt->{verbose} );
|
|
}
|
|
|
|
# Print normal messages
|
|
sub log_info {
|
|
my $msg = shift;
|
|
print $msg . "\n" if ( not $opt->{quiet} );
|
|
}
|
|
|
|
# Print error messages
|
|
sub log_error {
|
|
my $msg = shift;
|
|
print $msg . "\n";
|
|
}
|
|
|
|
# Create a watcher for a specific directory
|
|
sub create_watcher {
|
|
my ($inotify, $dir) = @_;
|
|
log_verbose("Start watching folder $dir");
|
|
$inotify->watch ($dir, IN_CLOSE_WRITE | IN_MOVED_TO, sub {
|
|
my $event = shift;
|
|
my $candidate = $event->fullname;
|
|
handle_submit($candidate);
|
|
});
|
|
}
|
|
|
|
# takes the path of an SRPM to rebuild,
|
|
# build it with mock, sign the result, update the repo
|
|
# and sync to remote mirrors if defined
|
|
sub handle_submit {
|
|
my $srpm = shift;
|
|
if (not -f $srpm){
|
|
log_verbose("$srpm isn't a file, ignoring");
|
|
return;
|
|
}
|
|
if ($srpm !~ m/src\.rpm$/i){
|
|
log_verbose("New file $srpm isn't an src.rpm file, ignoring");
|
|
return;
|
|
}
|
|
log_info("New file to process $srpm");
|
|
my $submiter = getpwuid(stat($srpm)->uid);
|
|
my $email;
|
|
log_info("File submited by $submiter");
|
|
if (defined $ldap){
|
|
$email = user2email($submiter);
|
|
if (not defined $email){
|
|
log_verbose("LDAP returned no result");
|
|
}
|
|
}
|
|
if (defined $email){
|
|
log_verbose("Notifications will be sent to $email");
|
|
} else {
|
|
log_verbose("No email address for $submiter, no notification will be sent");
|
|
}
|
|
my $src_pkg = RPM2->open_package($srpm);
|
|
if (not $src_pkg->is_source_package){
|
|
log_verbose("Couldn't parse $srpm as a valid srpm");
|
|
return;
|
|
}
|
|
my $target = basename(dirname($srpm));
|
|
if (not defined $conf->{targets}->{$target}){
|
|
log_info("$srpm submited for target $target, but it's not defined in the configuration");
|
|
}
|
|
foreach my $arch (@{$conf->{targets}->{$target}}){
|
|
my $job_id = $src_pkg->as_nvre() . '-' . time();
|
|
my $result = $conf->{paths}->{builds} . '/' . $submiter . '/' . $target . '-' . $arch . '/' . $job_id;
|
|
log_info("Rebuilding $srpm for $target/$arch in $result (job ID $job_id)");
|
|
make_path($result);
|
|
my $mock_msg;
|
|
foreach my $out (qx(mock -r $target-$arch --resultdir=$result $srpm 2>&1)){
|
|
chomp $out;
|
|
$mock_msg .= $out;
|
|
log_info("[$job_id] $out");
|
|
}
|
|
if ($? != 0) {
|
|
log_info("[$job_id] Build submited by $submiter failed");
|
|
handle_error($job_id, 'Mock build', $mock_msg);
|
|
return;
|
|
}
|
|
my $repo_dir = $conf->{paths}->{repo};
|
|
my $repo_cache_dir = $conf->{paths}->{repo_cache};
|
|
if ($src_pkg->release =~ m/\.(beta|git\.)/){
|
|
$repo_dir .= '/testing';
|
|
$repo_cache_dir .= '/testing';
|
|
}
|
|
$repo_dir .= '/' . $target;
|
|
$repo_cache_dir .= '/' . $target;
|
|
find({
|
|
wanted => sub {
|
|
return if (not -f);
|
|
return if (not $_ =~ m/\.rpm$/);
|
|
my $built_pkg = $_;
|
|
log_info("[$job_id] Signing package $built_pkg");
|
|
# Note : the optional passphrase for the gpg key is in rpmmacros
|
|
qx(rpm --addsign $built_pkg);
|
|
if ($? != 0) {
|
|
log_info("[$job_id] Signing failed");
|
|
handle_error($job_id, 'Package signature error', "Command rpm --addsign $built_pkg failed");
|
|
return;
|
|
}
|
|
# Open the package without checking the signature, as the key might not be present in the
|
|
# rpm trusted store
|
|
my $pkg = RPM2->open_package($built_pkg, RPM2->_rpmvsf_nosignatures);
|
|
my $dest = $repo_dir;
|
|
if ($pkg->is_source_package){
|
|
$dest .= '/SRPMS';
|
|
} else {
|
|
# the resulting RPM can be noarch, so use this instead of $arch
|
|
$dest .= '/' . $pkg->arch;
|
|
}
|
|
log_info("[$job_id] Moving $built_pkg to the repo $dest");
|
|
make_path($dest);
|
|
make_path($repo_cache_dir);
|
|
move $built_pkg, $dest . '/' . basename($built_pkg);
|
|
}
|
|
}, $result);
|
|
log_info("[$job_id] Updating repo metadata for $target");
|
|
qx(createrepo --checksum sha -x "*debuginfo*" --update -c $repo_cache_dir $repo_dir);
|
|
if ($? != 0) {
|
|
log_info("[$job_id] Createrepo failed");
|
|
handle_error(
|
|
$job_id,
|
|
'Createrepo error',
|
|
"Command createrepo --checksum sha -x \"*debuginfo*\" --update -c $repo_cache_dir $repo_dir"
|
|
);
|
|
return;
|
|
}
|
|
log_info("[$job_id] Building package finished");
|
|
# Now push to mirrors if defined
|
|
if (defined $conf->{mirror} and defined $conf->{mirror}->{push}){
|
|
foreach my $mirror (@{$conf->{mirror}->{push}}){
|
|
log_info("[$job_id] syncing repo to $mirror->{dest}");
|
|
my $rsync_cmd = 'rsync ';
|
|
if (defined $mirror->{rsync_opts}){
|
|
$rsync_cmd .= join(' ', @{$mirror->{rsync_opts}});
|
|
} else {
|
|
$rsync_cmd .= join(' ', @{$conf->{mirror}->{rsync_opts}});
|
|
}
|
|
$rsync_cmd .= ' ' . $conf->{paths}->{repo} . '/ ' . $mirror->{dest} . '/';
|
|
log_verbose("[$job_id] Running command $rsync_cmd");
|
|
foreach my $out (qx($rsync_cmd 2>&1)){
|
|
chomp $out;
|
|
log_verbose("[$job_id] $out");
|
|
}
|
|
if ($? != 0) {
|
|
log_info("[$job_id] Syncing to $mirror->{dest} failed");
|
|
handle_error($job_id, 'Mirror update error', "Command $rsync_cmd failed");
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
if (defined $email){
|
|
my $body = "Resulting RPM are available in $conf->{paths}->{repo}/$target";
|
|
if (defined $conf->{mirror} and defined $conf->{mirror}->{push}){
|
|
$body .= "\nand have been synced to the following mirror:\n";
|
|
foreach my $mirror (@{$conf->{mirror}->{push}}){
|
|
$body .= "$mirror->{dest}\n";
|
|
}
|
|
}
|
|
send_notification(
|
|
$email,
|
|
"Rebuilding " . $src_pkg->as_nvre() . " for $target/$arch succeded",
|
|
$body
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
# Handle errors. Log it, and notify the admin
|
|
sub handle_error {
|
|
my $job_id = shift;
|
|
my $step = shift;
|
|
my $err = shift;
|
|
my $dest = shift;
|
|
|
|
log_error( $err );
|
|
if ( defined $conf->{notify}->{to} ) {
|
|
send_notification(
|
|
$conf->{notify}->{to},
|
|
"Error while building $job_id",
|
|
"Building $job_id failed at step '$step'. The error was\n$err\n"
|
|
);
|
|
}
|
|
if ( defined $dest ) {
|
|
send_notification(
|
|
$dest,
|
|
"Error while building $job_id",
|
|
"Building $job_id failed at step '$step'. The error was\n$err\n"
|
|
);
|
|
}
|
|
}
|
|
|
|
# Send an email message
|
|
sub send_notification {
|
|
my $to = shift;
|
|
my $subject = shift;
|
|
my $body = shift;
|
|
my $mail = Email::MIME->create(
|
|
header_str => [
|
|
From => $conf->{notify}->{from},
|
|
To => $to,
|
|
Subject => $subject
|
|
],
|
|
attributes => {
|
|
charset => 'utf-8',
|
|
encoding => 'base64'
|
|
},
|
|
body_str => $body
|
|
);
|
|
my $transport = Email::Sender::Transport::Sendmail->new();
|
|
sendmail( $mail, { transport => $transport } );
|
|
}
|
|
|
|
# Lookup in LDAP if we can get the email address of a user
|
|
sub user2email {
|
|
my $user = shift;
|
|
if (not defined $ldap or not defined $conf->{ldap}->{search_base} or not defined $conf->{ldap}->{search_filter}){
|
|
return;
|
|
}
|
|
my $filter = $conf->{ldap}->{search_filter};
|
|
$filter =~ s/\{user\}/$user/g;
|
|
log_verbose("Searching in $conf->{ldap}->{search_base} with filter $filter");
|
|
my $results = $ldap->search(
|
|
base => $conf->{ldap}->{search_base},
|
|
filter => $filter,
|
|
attrs => [ $conf->{ldap}->{email_attr} ]
|
|
);
|
|
if ($results->code){
|
|
log_verbose("Error occured while searching in LDAP : " . $results->error);
|
|
return;
|
|
}
|
|
if ($results->count != 1){
|
|
log_verbose("Searching returned " . $results->count . "result(s), while it should have returned 1");
|
|
return;
|
|
}
|
|
return $results->entry(0)->get_value( $conf->{ldap}->{email_attr} );
|
|
}
|