Advertisement
smbarbour

MCUBuildBot

Feb 20th, 2013
100
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Perl 10.28 KB | None | 0 0
  1. #!/usr/bin/perl -w
  2. use strict;
  3. use feature "state";
  4. use File::Basename;
  5. use IO::CaptureOutput qw{:all};
  6. use List::MoreUtils qw{any none};
  7. use LWP::UserAgent;
  8. use Net::SFTP::Foreign;
  9. use Scalar::Util qw(looks_like_number);
  10. use String::Util 'trim';
  11. use Switch;
  12. use POE;
  13. use POE::Component::IRC;
  14. use POE::Component::IRC::Plugin::Console;
  15. use POE::Component::IRC::Plugin::NickServID;
  16. use POE::Component::IRC::Plugin::BotAddressed;
  17. use POE::Component::IRC::Plugin::Logger;
  18. use WWW::Google::URLShortener;
  19. use DBI;
  20.  
  21. my $google_api = '#### Redacted ####';
  22. my $shortener = WWW::Google::URLShortener->new($google_api);
  23. my $ua = new LWP::UserAgent;
  24. my $botdir = "/opt/MCU-build";
  25. my $basedir = "/var/workspace/";
  26. # Color contants
  27. my $WHITE = "\x0300";
  28. my $BLACK = "\x0301";
  29. my $BLUE = "\x0302";
  30. my $GREEN = "\x0303";
  31. my $RED = "\x0304";
  32. my $BROWN = "\x0305";
  33. my $PURPLE = "\x0306";
  34. my $ORANGE = "\x0307";
  35. my $YELLOW = "\x0308";
  36. my $LIGHT_GREEN = "\x0309";
  37. my $TEAL = "\x0310";
  38. my $LIGHT_CYAN = "\x0311";
  39. my $LIGHT_BLUE = "\x0312";
  40. my $PINK = "\x0313";
  41. my $GREY = "\x0314";
  42. my $LIGHT_GREY = "\x0315";
  43. my $NORMAL = "\x0f";
  44.  
  45. my $dbh = DBI->connect("dbi:SQLite:dbname=build.db","","") or die $DBI::errstr;
  46. #my @tables = $dbh->tables();
  47. my @branches = ('master','develop');
  48. my @ops = ('smbarbour','allaryin');
  49. my $active = 0;
  50. $dbh->do('CREATE TABLE IF NOT EXISTS Builds(branch PRIMARY KEY, gitcommit, buildnumber)');
  51. my $updateBuild = $dbh->prepare('REPLACE INTO Builds(branch, gitcommit, buildnumber) VALUES (?, ?, ?)');
  52. my $getLastBuild = $dbh->prepare('SELECT gitcommit, buildnumber FROM Builds where branch = ?');
  53.  
  54. my $topic = "";
  55.  
  56. my ($irc) = POE::Component::IRC->spawn();
  57.  
  58. POE::Session->create(
  59.         inline_states => {
  60.         _start => \&bot_start,
  61.         irc_001 => \&on_connect,
  62.         irc_public => \&on_public,
  63.         irc_msg => \&on_private,
  64.         irc_join => \&on_join,
  65.         irc_disconnected => \&on_disconnect,
  66.         irc_332 => \&on_topicResponse,
  67.         irc_topic => \&on_topicChange,
  68.         git_poll => \&Poll,
  69.         },
  70.         );
  71.  
  72. sub bot_start {
  73.     my $kernel = $_[KERNEL];
  74.     $irc->yield(register => "all");
  75.     my $nick = "MCUBuildBot";
  76.     $irc->yield(connect => {
  77.             Nick => $nick,
  78.             Username => 'buildbot',
  79.             Ircname => q{MCUpdater's BuildBot},
  80.             Server => 'irc.esper.net',
  81.             Port => '6667',
  82.             }
  83.             );
  84.  
  85.     $irc->plugin_add( 'NickServID', POE::Component::IRC::Plugin::NickServID->new( Password => '#### Redacted ####' ) );
  86.     $kernel->delay(git_poll => 10);
  87.     $irc->yield(topic => "#MCUpdater");
  88. }
  89.  
  90. sub on_connect {
  91.     $irc->yield(join => '#MCUpdater');
  92.     return;
  93. }
  94.  
  95. sub on_disconnect {
  96.     if ($active == 1) {
  97.         &bot_start;
  98.     }
  99.     return;
  100. }
  101.  
  102. sub on_topicChange {
  103.     my ($kernel, $who, $where, $what) = @_[KERNEL, ARG0, ARG1, ARG2];
  104.     $topic = $what;
  105.     print qq{Topic in $where is now "$topic" changed by $who.\n};
  106. }
  107.  
  108. sub on_topicResponse {
  109.     my ($kernel, $chan, $top) = @_[KERNEL, ARG0, ARG1];
  110.     $topic = substr($top,index($top,":")+1,255);
  111.     print "Topic is: $topic\n";
  112. }
  113.  
  114. sub on_public {
  115.     my ($kernel, $who, $where, $msg) = @_[KERNEL, ARG0, ARG1, ARG2];
  116.     my $nick = (split /!/, $who)[0];
  117.     my $channel = $where->[0];
  118.     my $isOp = 0;
  119.     foreach (@ops) {
  120.         if ( $nick eq $_ ) { $isOp = 1; }
  121.     }
  122.     if ($msg =~ /^\!/) {
  123.         my ($command, @cmdargs) = (split / /, $msg);
  124.         switch ($command)
  125.         {
  126.             case '!build' {
  127.                 if ($isOp != 1) {
  128.                     $irc->yield(privmsg => $channel, "You are not allowed to do that.");
  129.                 } else {
  130.                     my ($branch, $force, $overrideNum) = @cmdargs[0, 1, 2];
  131.                     if (none { /$branch/ } @branches) {
  132.                         $irc->yield(privmsg => $channel, "Branch $branch is not configured for build.");
  133.                         return;
  134.                     }
  135.                     my $noChange = 0;
  136.                     chdir("$basedir$branch/MCUpdater");
  137.                     open(PULL, 'git pull origin |');
  138.                     my $pullOutput = "";
  139.                     while(<PULL>){
  140.                         $pullOutput .= $_ . "\n";
  141. #$irc->yield(privmsg => $channel, $_);
  142.                     }
  143.                     close(PULL);
  144.                     if (index($pullOutput, "Already up-to-date") != -1) {
  145.                         $noChange = 1;
  146.                     }
  147.                     if ($noChange == 1 && $force ne "force") {
  148.                         $irc->yield(privmsg => $channel, "No changes to be built on branch $branch.");
  149.                         return;
  150.                     } else {
  151.                         $irc->yield(privmsg => $channel, "Beginning build on branch $branch.");
  152.                     }
  153.                     &DoBuild($channel, $branch, $force, $overrideNum);
  154.                 }
  155.             }
  156.             case /\!l(c$|astcommit$)/ {
  157.                 my @localbranches = @cmdargs;
  158.                 if (@localbranches == 0) { @localbranches = @branches; }
  159.                 foreach my $branch (@localbranches) {
  160.                     if (none { /$branch/ } @branches) {
  161.                         $irc->yield(privmsg => $channel, "Branch $branch is not configured.");
  162.                     } else {
  163.                         chdir("$basedir$branch/MCUpdater");
  164.                         my $response = `git log -1 --format="format:%H~~~~~%cn~~~~~%s"`;
  165.                         my ($commit, $author, $title) = split('~~~~~',$response);
  166.                         $irc->yield(privmsg => $channel, "Last commit on branch $branch:");
  167.                         $irc->yield(privmsg => $channel, &ShortenCommit($commit) . " <$author> $title");
  168.                     }
  169.                 }
  170.             }
  171.             case /\!c($|ommits$)/ {
  172.                 my $branch = $cmdargs[0];
  173.                 if (none { /$branch/ } @branches) {
  174.                     $irc->yield(privmsg => $channel, "Branch $branch is not configured.");
  175.                     return;
  176.                 }
  177.                 my $count = $cmdargs[1];
  178.                 if (!defined $count) {
  179.                     $count = 3;
  180.                 }
  181.                 if (looks_like_number($count)) {
  182.                     open(GIT, qq{git log -$count --format="format:%H~~~~~%cn~~~~~%s" |});
  183.                     my @lines = <GIT>;
  184.                     close(GIT);
  185.                     $irc->yield(privmsg => $channel, "Last $count commit(s) on branch $branch:");
  186.                     foreach my $line (@lines) {
  187.                         my ($commit, $author, $title) = split('~~~~~',$line);
  188.                         $irc->yield(privmsg => $channel, &ShortenCommit($commit) . " <$author> $title");
  189.                     }
  190.                 } else {
  191.                     $irc->yield(privmsg => $channel, "$count is not a number.");
  192.                 }
  193.             }
  194.         }
  195.     }
  196.     print "<$nick> $msg\n";
  197.     return;
  198. }
  199.  
  200. sub on_private {
  201.     my ($kernel, $from, $to, $msg, $identified) = @_[KERNEL, ARG0, ARG1, ARG2, ARG3];
  202.     my $nick = (split /!/, $from)[0];
  203.     my ($command, @cmdargs) = (split / /, $msg);
  204.     my $isOp = 0;
  205.     foreach (@ops) {
  206.         if ( $nick eq $_ ) { $isOp = 1; }
  207.     }
  208.     if ($command eq "shutdown" && $isOp == 1) {
  209.         $active = 0;
  210.         $irc->yield(quit => "Shutting down.");
  211.         $irc->delay(git_poll => undef);
  212.         $irc->yield(shutdown => "");
  213.     }
  214.     return;
  215. }
  216.  
  217. sub on_join {
  218.     return;
  219. }
  220.  
  221. sub Poll {
  222.     my $kernel = $_[KERNEL];
  223.     &CheckBranch('master');
  224.     &CheckBranch('develop');
  225.     $kernel->delay(git_poll => 300);
  226. }
  227.  
  228. sub CheckBranch {
  229.     my $branch = $_[0];
  230.     my $channel = "#MCUpdater";
  231.     my $noChange = 0;
  232.     chdir("$basedir$branch/MCUpdater");
  233.     open(PULL, 'git pull origin |');
  234.     my $pullOutput = "";
  235.     while(<PULL>){
  236.         $pullOutput .= $_ . "\n";
  237.     }
  238.     close(PULL);
  239.     if (index($pullOutput, "Already up-to-date") != -1) {
  240.         $noChange = 1;
  241.         print "No changes for branch: $branch\n";
  242.     }
  243.     if ($noChange == 0) {
  244.         $irc->yield(privmsg => $channel, "Beginning build on branch $branch.");
  245.     } else {
  246.         return;
  247.     }
  248.     &DoBuild($channel, $branch);
  249.     return;
  250. }
  251.  
  252. sub DoBuild {
  253.     my ($channel, $branch, $force, $overrideNum) = @_[0,1,2,3];
  254.     $getLastBuild->execute($branch);
  255.     my $result = $getLastBuild->fetch;
  256.     my $buildNum = 1;
  257.     my $commit = "";
  258.     if (defined $result) {
  259.         $commit = $$result[0];
  260.         $buildNum = $$result[1];
  261.         $buildNum++;
  262.     }
  263.     if (defined $overrideNum) { $buildNum = $overrideNum; }
  264.     my $newCommit = `git log -1 --format=format:%H`;
  265.     $irc->yield(privmsg => $channel, "Newest commit on branch $branch: $newCommit");
  266.     my $logcmd;
  267.     if ($commit eq "") {
  268.         $logcmd = qq{git log -5 --format="format:%H~~~~~%cn~~~~~%s" |};
  269.     } else {
  270.         $logcmd = qq{git log $commit..$newCommit --format="format:%H~~~~~%cn~~~~~%s" |};
  271.     }
  272.     open (CHANGES, $logcmd);
  273.     my @changes = <CHANGES>;
  274.     print "Lines: " . @changes . "\n";
  275.     close(CHANGES);
  276.     foreach my $change (@changes) {
  277.         my ($commit, $author, $title) = split('~~~~~',$change);
  278.         $irc->yield(privmsg => $channel, &ShortenCommit($commit) . " <$author> $title");
  279.     }
  280.     $ENV{BUILD_NUMBER}=$buildNum;
  281.     $ENV{GIT_BRANCH}=$branch;
  282.     $ENV{GIT_COMMIT}=$newCommit;
  283.     my @build_args = qw{ant -f build-client.xml};
  284.     my ($combined, $success, $exit) = capture_exec_combined(@build_args);
  285.     open(BUILD, ">>", qq{$botdir/logs/build-log.txt});
  286.     print BUILD qq{Build ($branch $buildNum) successful=$success ($exit)\n$combined\n};
  287.     close(BUILD);
  288.     if ($success) {
  289.         my @build_args = qw{ant -f build-serverutility.xml};
  290.         my ($combined, $success, $exit) = capture_exec_combined(@build_args);
  291.         open(BUILD, ">>", qq{$botdir/logs/build-log.txt});
  292.         print BUILD qq{Build ($branch $buildNum) successful=$success ($exit)\n$combined\n};
  293.         close(BUILD);
  294.         if ($success) {
  295.             $irc->yield(privmsg => $channel, $GREEN . "Build $buildNum successful on branch $branch" . $NORMAL);
  296.             $updateBuild->execute($branch, $newCommit, $buildNum);
  297.             my $sftp = Net::SFTP::Foreign->new('northumberland.dreamhost.com', user => 'mcujenkins', key_path => "$botdir/id_mcujenkins", ssh_cmd => '/usr/bin/ssh');
  298.             $sftp->error and $irc->yield(privmsg => $channel, "Unable to establish SFTP connection: " . $sftp->error);
  299.             $sftp->setcwd("/home/mcujenkins/files.mcupdater.com/$branch/");
  300.             my @files = glob "dist/*";
  301.             foreach my $file (@files) {
  302.                 open FILE, $file;
  303.                 binmode FILE;
  304.                 my $shortname = basename($file);
  305.                 $sftp->put(\*FILE,$shortname);
  306.                 close FILE;
  307.                 if ($shortname =~ /MCUpdater/) {
  308.                     my $shorturl = $shortener->shorten_url("http://files.mcupdater.com/$branch/$shortname");
  309.                     my @elements = split(/\s\|\s/, $topic);
  310.                     for (my $x = 0; $x < @elements; $x++) {
  311.                         print $elements[$x] . "\n";
  312.                         if (substr($elements[$x],0,14) eq 'Latest release' && $branch eq 'master') {
  313.                             $elements[$x] = "Latest release: $shorturl";
  314.                         } elsif (substr($elements[$x],0,10) eq 'Latest dev' && $branch eq 'develop') {
  315.                             $elements[$x] = "Latest dev: $shorturl";
  316.                         }
  317.                     }
  318.                     $topic = join(' | ', @elements);
  319.                     $irc->yield(topic => $channel, $topic);
  320.                 }
  321.             }
  322.         } else {
  323.             $irc->yield(privmsg => $channel, $RED . "ServerUtility build failed on branch $branch" . $NORMAL);
  324.         }
  325.     } else {
  326.         $irc->yield(privmsg => $channel, $RED . "Client build failed on branch $branch" . $NORMAL);
  327.     }
  328.     return;
  329. }
  330.  
  331. sub ShortenCommit {
  332.     my $commit = $_[0];
  333.     my $response = $ua->post("http://git.io", {url=>"https://github.com/smbarbour/MCUpdater/commit/$commit",});
  334.     return $response->header("Location");
  335. }
  336.  
  337. $active = 1;
  338. $poe_kernel->run();
  339. exit 0;
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement