codepot/etc/post-commit.in

566 lines
13 KiB
Plaintext
Raw Normal View History

2014-05-16 15:49:16 +00:00
#!/usr/bin/perl
use strict;
use Config::Simple;
use DBI;
use File::Basename;
use POSIX;
2014-05-16 15:49:16 +00:00
use SVN::Core;
use SVN::Repos;
use SVN::Fs;
use Net::LDAP;
use Net::LDAP qw(LDAP_SUCCESS);
use URI;
use Mail::Sendmail;
use LWP::UserAgent;
2014-05-16 15:49:16 +00:00
my $CFG_FILE = '@CFGDIR@/codepot.ini';
my $REPOFS = $ARGV[0];
my $REPOBASE = basename($REPOFS);
2014-05-16 15:49:16 +00:00
my $REV = $ARGV[1];
my $QC = '';
2014-05-16 15:49:16 +00:00
sub get_config
{
my $cfg = new Config::Simple();
2014-05-16 15:49:16 +00:00
if (!$cfg->read($CFG_FILE))
{
return undef;
}
2014-05-16 15:49:16 +00:00
my $config = {
login_model => $cfg->param('login_model'),
ldap_server_uri => $cfg->param('ldap_server_uri'),
ldap_server_protocol_version => $cfg->param('ldap_server_protocol_version'),
ldap_auth_mode => $cfg->param('ldap_auth_mode'),
ldap_userid_format => $cfg->param('ldap_userid_format'),
ldap_password_format => $cfg->param('ldap_password_format'),
ldap_admin_binddn => $cfg->param('ldap_admin_binddn'),
ldap_admin_password => $cfg->param('ldap_admin_password'),
ldap_userid_search_base => $cfg->param('ldap_userid_search_base'),
ldap_userid_search_filter => $cfg->param('ldap_userid_search_filter'),
ldap_mail_attribute_name => $cfg->param('ldap_mail_attribute_name'),
database_hostname => $cfg->param("database_hostname"),
database_port => $cfg->param("database_port"),
database_username => $cfg->param("database_username"),
database_password => $cfg->param("database_password"),
database_name => $cfg->param("database_name"),
database_driver => $cfg->param("database_driver"),
database_prefix => $cfg->param("database_prefix"),
database_store_gmt => $cfg->param("database_store_gmt"),
email_sender => $cfg->param("email_sender"),
commit_notification => $cfg->param("commit_notification"),
commit_notification_url => $cfg->param("commit_notification_url")
};
2014-05-16 15:49:16 +00:00
return $config;
2014-05-16 15:49:16 +00:00
}
sub open_database
{
my ($cfg) = @_;
my $dbtype = $cfg->{database_driver};
my $dbname = $cfg->{database_name};
my $dbhost = $cfg->{database_hostname};
my $dbport = $cfg->{database_port};
if ($dbtype eq 'postgre') { $dbtype = 'Pg'; }
elsif ($dbtype eq 'oci8') { $dbtype = 'Oracle'; }
elsif ($dbtype eq 'mysqli') { $dbtype = 'mysql'; }
elsif ($dbtype eq 'sqlite') { $dbtype = 'SQLite'; }
my $dbstr;
my $dbuser;
my $dbpass;
if ($dbtype eq 'Oracle')
{
$QC = '"';
$dbstr = "DBI:$dbtype:";
$dbuser = $cfg->{database_username} . '/' . $cfg->{database_password} . '@' . $dbhost;
$dbpass = '';
}
elsif ($dbtype eq 'SQLite')
{
$dbstr = "DBI:$dbtype:database=$dbhost;";
$dbuser = $cfg->{database_username};
$dbpass = $cfg->{database_password};
}
else
{
$dbstr = "DBI:$dbtype:database=$dbname;";
if (length($dbhost) > 0) { $dbstr .= "host=$dbhost;"; }
if (length($dbport) > 0) { $dbstr .= "port=$dbport;"; }
$dbuser = $cfg->{database_username};
$dbpass = $cfg->{database_password};
}
2014-05-16 15:49:16 +00:00
my $dbh = DBI->connect(
$dbstr, $dbuser, $dbpass,
2014-05-16 15:49:16 +00:00
{ RaiseError => 0, PrintError => 0, AutoCommit => 0 }
);
return $dbh;
}
sub close_database
{
my ($dbh) = @_;
$dbh->disconnect ();
}
sub find_issue_reference_in_commit_message
{
my ($dbh, $prefix, $projectid, $revision, $commit_message) = @_;
# find [[#IXXXX]]
my @issue_ids = ($commit_message =~ /\[\[#I(\d+)\]\]/g);
# find #XXXX
my @issue_ids2 = ($commit_message =~ /(^|[^#])#(\d+)(\D|$)/g);
# find unique issue ids in the findings.
my %tmp;
@tmp{@issue_ids} = 1;
for (my $i = 0; $i < scalar(@issue_ids2); $i += 3)
{
my $id = @issue_ids2[$i + 1];
@tmp{$id} = 1;
}
my @unique_issue_ids = keys(%tmp);
$dbh->begin_work ();
my $query = $dbh->prepare("DELETE FROM ${QC}${prefix}issue_coderev${QC} WHERE ${QC}codeproid${QC}=? AND ${QC}coderev${QC}=?");
if (!$query || !$query->execute($projectid, $revision))
{
my $errstr = $dbh->errstr();
if ($query) { $query->finish (); }
$dbh->rollback ();
return (-1, $errstr);
}
$query->finish ();
for my $issue_id(@unique_issue_ids)
{
my $query = $dbh->prepare("INSERT INTO ${QC}${prefix}issue_coderev${QC} (${QC}projectid${QC},${QC}issueid${QC},${QC}codeproid${QC},${QC}coderev${QC}) VALUES (?,?,?,?)");
if ($query)
{
# ignore errors
$query->execute ($projectid, $issue_id, $projectid, $revision);
$query->finish ();
}
}
$dbh->commit ();
return (0, undef);
}
2014-05-16 15:49:16 +00:00
sub write_commit_log
{
my ($dbh, $prefix, $projectid, $revision, $userid, $store_gmt) = @_;
2014-05-16 15:49:16 +00:00
#+------+---------+-----------+---------------------------+---------------------+---------------+-----------------+
#| id | type | projectid | message | createdon | action | userid |
#+------+---------+-----------+---------------------------+---------------------+---------------+-----------------+
#| 895 | code | codepot | svn,codepot,72 | 2011-10-10 14:26:43 | commit | hyunghwan.chung |
my $message = "svn,$projectid,$revision";
my $timestamp;
if (($store_gmt =~ /^\d+?$/ && int($store_gmt) != 0) || (lc($store_gmt) eq 'yes'))
{
$timestamp = POSIX::strftime('%Y-%m-%d %H:%M:%S', gmtime());
}
else
{
$timestamp = POSIX::strftime('%Y-%m-%d %H:%M:%S', localtime());
}
# the PHP side is executing ALTER SESSION SET NLS_TIMESTAMP_FORMAT='YYYY-MM-DD HH24:MI:SS.FF.
# do i have to do it here or use the database time (CURRENT_TIMESTAMP) instead?
# make sure that you have the same time between the app server and the data server.
# to minize side-effect of using the time of data server.
#my $createdon = POSIX::strftime ('%Y-%m-%d %H:%M:%S', localtime());
2014-05-16 15:49:16 +00:00
$dbh->begin_work ();
#my $query = $dbh->prepare ("INSERT INTO ${QC}${prefix}log${QC} (${QC}type${QC},${QC}projectid${QC},${QC}message${QC},${QC}createdon${QC},${QC}action${QC},${QC}userid${QC}) VALUES (?,?,?,?,?,?)");
#if (!$query || !$query->execute('code', $projectid, $message, $createdon, 'commit', $userid))
my $query = $dbh->prepare ("INSERT INTO ${QC}${prefix}log${QC} (${QC}type${QC},${QC}projectid${QC},${QC}message${QC},${QC}createdon${QC},${QC}action${QC},${QC}userid${QC}) VALUES (?,?,?,?,?,?)");
if (!$query || !$query->execute('code', $projectid, $message, $timestamp, 'commit', $userid))
2014-05-16 15:49:16 +00:00
{
my $errstr = $dbh->errstr();
if ($query) { $query->finish (); }
2014-05-16 15:49:16 +00:00
$dbh->rollback ();
return (-1, $errstr);
2014-05-16 15:49:16 +00:00
}
$query->finish ();
2014-05-16 15:49:16 +00:00
$dbh->commit ();
return (0, undef);
2014-05-16 15:49:16 +00:00
}
sub get_author
{
my $pool = SVN::Pool->new(undef);
my $svn = eval { SVN::Repos::open ($REPOFS, $pool) };
if (!defined($svn))
{
print (STDERR "Cannot open svn - $REPOFS\n");
return undef;
}
my $fs = $svn->fs();
if (!defined($fs))
{
print (STDERR "Cannot open fs - $REPOFS\n");
return undef;
}
my $author = $fs->revision_prop ($REV, 'svn:author');
return $author;
}
sub get_commit_message
{
my $pool = SVN::Pool->new(undef);
my $svn = eval { SVN::Repos::open ($REPOFS, $pool) };
if (!defined($svn))
{
print (STDERR "Cannot open svn - $REPOFS\n");
return undef;
}
my $fs = $svn->fs();
if (!defined($fs))
{
print (STDERR "Cannot open fs - $REPOFS\n");
return undef;
}
my $logmsg = $fs->revision_prop($REV, 'svn:log');
return $logmsg;
}
sub format_string
{
my ($fmt, $userid, $password) = @_;
my $out = $fmt;
$out =~ s/\$\{userid\}/$userid/g;
$out =~ s/\$\{password\}/$password/g;
return $out;
}
sub get_author_email_ldap
{
my ($cfg, $userid) = @_;
my $uri = URI->new ($cfg->{ldap_server_uri});
my $ldap = Net::LDAP->new (
$uri->host,
scheme => $uri->scheme,
port => $uri->port,
version => $cfg->{ldap_server_protocol_version}
);
if (!defined($ldap))
{
print (STDERR 'Cannot create LDAP');
return (-1, undef);
}
my $f_rootdn = format_string($cfg->{ldap_admin_binddn}, $userid, '');
my $f_rootpw = format_string($cfg->{ldap_admin_password}, $userid, '');
my $res = $ldap->bind($f_rootdn, password => $f_rootpw);
if ($res->code != LDAP_SUCCESS)
{
print (STDERR "Cannot bind LDAP as $f_rootdn - " . $res->error());
$ldap->unbind();
return (-1, undef);
}
my $f_basedn = '';
my $f_filter = '';
if ($cfg->{ldap_auth_mode} == 2)
{
$f_basedn = format_string($cfg->{ldap_userid_search_base}, $userid, '');
$f_filter = format_string($cfg->{ldap_userid_search_filter}, $userid, '');
$res = $ldap->search(base => $f_basedn, scope => 'sub', filter => $f_filter);
if ($res->code != LDAP_SUCCESS)
{
$ldap->unbind();
return (-1, undef);
}
my $entry = $res->entry(0); # get the first entry only
if (!defined($entry))
{
$ldap->unbind();
return (0, undef);
}
$f_basedn = $entry->dn();
}
else
{
$f_basedn = format_string($cfg->{ldap_userid_format}, $userid, '');
}
$f_filter = '(' . $cfg->{ldap_mail_attribute_name} . '=*)';
$res = $ldap->search(base => $f_basedn, scope => 'sub', filter => $f_filter);
if ($res->code != LDAP_SUCCESS)
{
$ldap->unbind();
return (0, undef);
}
my $entry = $res->entry(0); # get the first entry only
if (!defined($entry))
{
$ldap->unbind();
return (0, undef);
}
my $xret = 0;
my $email = '';
my @attrs = $entry->attributes ();
foreach my $attr (@attrs)
{
if ($attr eq $cfg->{ldap_mail_attribute_name})
{
$email = $entry->get_value ($attr);
$xret = 1;
last;
}
}
$ldap->unbind();
return ($xret, $email);
}
sub get_author_email_db
{
my ($cfg, $dbh, $prefix, $userid) = @_;
my $query = $dbh->prepare("SELECT ${QC}email${QC} FROM ${QC}${prefix}user_account${QC} WHERE ${QC}userid${QC}=?");
if (!$query || !$query->execute($userid))
{
return (-1, $dbh->errstr());
}
if (my @row = $query->fetchrow_array())
{
return (1, @row[0]);
}
return (0, undef);
}
sub email_message_to_project_members
{
my ($cfg, $dbh, $prefix, $projectid, $subject, $message) = @_;
my $query = $dbh->prepare("SELECT ${QC}userid${QC} FROM ${QC}${prefix}project_membership${QC} WHERE ${QC}projectid${QC}=?");
if (!$query || !$query->execute($projectid))
{
if ($query) { $query->finish (); }
return (-1, $dbh->errstr());
}
my @members;
while (my @row = $query->fetchrow_array())
{
push (@members, $row[0]);
}
$query->finish ();
my $recipients = '';
foreach my $member (@members)
{
my $xret;
my $email;
if ($cfg->{login_model} eq 'LdapLoginModel')
{
($xret, $email) = get_author_email_ldap($cfg, $member)
}
elsif ($cfg->{login_model} eq 'DbLoginModel')
{
($xret, $email) = get_author_email_db($cfg, $dbh, $prefix, $member);
}
else
{
$xret = -2;
$email = '';
}
if ($xret >= 1 && defined($email) && length($email) > 0)
{
if (length($recipients) > 0) { $recipients .= ', '; }
$recipients .= $email;
}
}
if (length($recipients) <= 0) { return (0, undef); }
my %mail = (
To => $recipients,
Subject => $subject,
Message => $message
);
if (length($cfg->{email_sender}) > 0)
{
$mail{From} .= $cfg->{email_sender};
}
Mail::Sendmail::sendmail (%mail);
return (1, undef);
}
sub format_commit_url
{
my ($fmt, $projectid, $author, $rev) = @_;
my $out = $fmt;
$out =~ s/\$\{PROJECTID\}/$projectid/g;
$out =~ s/\$\{AUTHOR\}/$author/g;
$out =~ s/\$\{REV\}/$rev/g;
return $out;
}
sub trigger_webhooks
{
my ($cfg, $dbh, $prefix, $projectid, $commit_message) = @_;
# find [skip ci], [no ci] or something similar
my @skip = ();
while ($commit_message =~ /\[(skip|no)[[:space:]]+([[:alpha:]]+)\]/g)
{
push (@skip, $2);
}
2021-09-13 13:05:51 +00:00
my $query = $dbh->prepare("SELECT ${QC}webhooks${QC} FROM ${QC}${prefix}project${QC} WHERE ${QC}id${QC}=?");
if (!$query || !$query->execute($projectid))
{
if ($query) { $query->finish (); }
return (-1, $dbh->errstr());
}
my $webhooks = '';
if (my @row = $query->fetchrow_array())
{
$webhooks = $row[0];
}
my $ua = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0 });
$ua->timeout (5);
foreach my $webhook (split(/\n/ ,$webhooks))
{
$webhook =~ s/^\s+|\s+$//g;
if ($webhook ne '')
{
my @tmp = split(/[[:space:]]+/, $webhook);
my $type = 'ci';
my $url = '';
if (scalar(@tmp) == 1) { $url = @tmp[0]; }
elsif (scalar(@tmp) == 2) { $type = @tmp[0]; $url = @tmp[1]; }
else { next; }
if (grep(/^$type$/, @skip)) { next; }
## TODO: some formatting on url?
my $res = $ua->get($url);
if ($res->is_success)
{
2021-09-13 13:05:51 +00:00
##print $res->decoded_content . "\n";
}
else
{
2021-09-13 13:05:51 +00:00
##print $res->status_line . "\n";
}
}
}
$query->finish ();
return (0, undef);
}
2014-05-16 15:49:16 +00:00
#------------------------------------------------------------
# MAIN
#------------------------------------------------------------
my $AUTHOR = get_author();
if (!defined($AUTHOR))
{
print (STDERR "Cannot get author for $REPOBASE $REV\n");
exit (1);
}
2014-05-16 15:49:16 +00:00
chomp ($AUTHOR);
my $cfg = get_config();
2014-05-16 15:49:16 +00:00
if (!defined($cfg))
{
print (STDERR "Cannot load codepot configuration file\n");
exit (1);
}
my $dbh = open_database($cfg);
2014-05-16 15:49:16 +00:00
if (!defined($dbh))
{
printf (STDERR "Cannot open database - %s\n", $DBI::errstr);
2014-05-16 15:49:16 +00:00
exit (1);
}
my $raw_commit_message = get_commit_message();
find_issue_reference_in_commit_message ($dbh, $cfg->{database_prefix}, $REPOBASE, $REV, $raw_commit_message);
write_commit_log ($dbh, $cfg->{database_prefix}, $REPOBASE, $REV, $AUTHOR, $cfg->{database_store_gmt});
if (lc($cfg->{commit_notification}) eq 'yes')
{
my $commit_subject = "Commit r$REV by $AUTHOR in $REPOBASE";
my $commit_message = '';
if ($cfg->{commit_notification_url} eq '')
{
$commit_message = $commit_subject;
}
else
{
$commit_message = format_commit_url($cfg->{commit_notification_url}, $REPOBASE, $AUTHOR, $REV);
}
if (defined($raw_commit_message))
{
$commit_message = $commit_message . "\n" . $raw_commit_message;
}
email_message_to_project_members ($cfg, $dbh, $cfg->{database_prefix}, $REPOBASE, $commit_subject, $commit_message);
}
trigger_webhooks ($cfg, $dbh, $cfg->{database_prefix}, $REPOBASE, $raw_commit_message);
2014-05-16 15:49:16 +00:00
close_database ($dbh);
exit (0);