我该如何使用Jenkins通知Skype聊天室构建状态?

9
我们公司使用Skype进行通讯,我想要在Jenkins构建失败(以及恢复)时能够向Skype聊天室发送警报。
我该怎么做?
3个回答

12

我使用了Skype Public API来完成这个任务。

我编写了一个Perl脚本,使用SkypeAPI CPAN模块处理与Skype的通信。它有点笨重,因为脚本需要在运行Skype的桌面上运行。我在自己的桌面上运行它,因为我的电脑总是开着的,但这意味着机器人在我们团队中看起来像是我。

最终结果非常好 - 每当Jenkins构建状态发生变化时,机器人会向任何注册了兴趣的Skype聊天发送消息,方法是输入*alert。此外,任何开发人员都可以通过输入*jenkins查看和分享最新的构建状态。

第一步 - 扩展SkypeAPI模块

现在,SkypeAPI模块相当基础。它在listen()方法中具有消息循环,检查来自Skype客户端的新事件,并在没有事件时休眠一段时间。

我想让我的脚本钩入这个循环,以便我可以让我的机器人定期检查Jenkins的RSS源,所以我在使用ActiveState软件包管理器安装后,对SkypeAPI.pm进行了以下修改:

我声明了新属性'idler'以及现有属性...

__PACKAGE__->mk_accessors(
  qw/api handler_list stop_listen idler/
);

我添加了一个方法来设置“空闲”回调函数,模块将调用该函数而不是休眠

sub register_idler {
    my $self = shift;
    my $ref_sub = shift;
    $self->idler($ref_sub);
}

最后,我修改了消息循环以在设置时调用空闲处理程序。
sub listen {
    my $self = shift;

    my $idler=$self->idler();

    $self->stop_listen(0);
    while (!$self->stop_listen) {
        my $message;
        {
            lock @message_list;
            $message = shift @message_list;
        }
        if (not defined $message) {
            if ($idler)
            {
                $self->idler->($self);
            }
            else
            {
                sleep 0.1;             
            }
            next;
        }
        for my $id (sort keys %{$self->handler_list}) {
            $self->handler_list->{$id}->($self, $message);
        }
    }
}

第二步 - 编写机器人脚本

现在模块已经更加强大,只需要编写一个脚本来充当机器人即可。这是我的脚本 - 我对原始脚本进行了一些编辑,因为它包含其他无关的功能,但它应该可以给你一个起点。

所有相关的模块都可以使用ActiveState软件包管理器安装。

use strict;
use SkypeAPI;
use LWP::Simple;
use Data::Dumper;
use dirtyRSS;
use Time::Local 'timegm';
use Math::Round;
use Storable;

#CHANGE THIS - where to get jenkins status from
my $jenkinsRss='http://username:password@jenkins.example.com/rssLatest';

my %commands=(
    'jenkins'   =>\&cmdJenkins,
    'alert'     =>\&cmdAlert,
    'noalert'   =>\&cmdNoAlert,
    'help'      =>\&cmdHelp,
);

my $helpMessage=<<HELP;
Who asked for help? Here's all the other special commands I know...

  *jenkins - show status of our platform tests
  *alert - add this room to get automatic notification of build status
  *noalert - cancel notifcations
  *help - displays this message
HELP


#status for jenkins tracking
my %builds;
my $lastJenkinsCheck=0;
my $alertRoomsFile='alert.rooms';
my $alertRooms={};

#store jenkins state
checkJenkins();

#because that was our first fetch, we'll have flagged everything as changed
#but it hasn't really, so we reset those flags
resetJenkinsChangeFlags();

#remember rooms we're supposed to alert
loadAlertRooms();

#attach to skype and enter message loop
my $skype = SkypeAPI->new();
my $attached=$skype->attach();
$skype->register_handler(\&onEvent);
$skype->register_idler(\&onIdle);
$skype->listen();

exit;

#here are the command handlers
sub cmdJenkins
{
    my ($chatId, $args)=@_;

    my $message="";
    foreach my $build (keys(%builds))
    {
        $message.=formatBuildMessage($build)."\n";

        #reset changed flag - we've just show the status
        $builds{$build}->{'changed'}=0;
    }

    chatmessage($chatId, $message);
}

sub cmdAlert
{
    my ($chatId, $args)=@_;
    addChatroomToAlerts($chatId,1);
}

sub cmdNoAlert
{
    my ($chatId, $args)=@_;
    addChatroomToAlerts($chatId,0);
}

sub cmdHelp
{
    my ($chatId, $args)=@_;
    chatmessage($chatId, $helpMessage);
}






#simple helper to transmit a message to a specific chatroom
sub chatmessage
{
    my ($chatId, $message)=@_;
    my $commandstr="CHATMESSAGE $chatId $message";
    my $command = $skype->create_command( { string => $commandstr}  );
    $skype->send_command($command);
}


#refreshes our copy of jenkins state, and will flag any builds
#which have changed state since the last check
sub checkJenkins{

    my $xml = get($jenkinsRss);
    my $tree = parse($xml);
    my $items=$tree->{'channel'}->[0]->{'item'};

    foreach my $item (@{$items})
    {
        my $title=$item->{'title'};
        my $link=$item->{'link'};
        my $built=$item->{'lastbuilddate'};

        #print Dumper($item);

        if ($title=~m/^(.*?) #(\d+)\s*(.*)$/)
        {
            my $build=$1;
            my $buildnumber=$2;
            my $status=$3;
            #print "$build\n$buildnumber\n$status\n$link\n$built\n\n";    

            #build in progress, ignore

            if (!exists($builds{$build}))
            {
                $builds{$build}={};
                $builds{$build}->{'status'}='';
                $builds{$build}->{'changed'}=0;
            }

            $builds{$build}->{'name'}=$build;

            if ($status eq '(?)')
            {
                $builds{$build}->{'in_progress'}=1;
                next; #don't update until complete
            }
            else
            {
                $builds{$build}->{'in_progress'}=0;
            }

            #is this status different to last status?
            if ($builds{$build}->{'status'} ne $status)
            {
                $builds{$build}->{'changed'}=1;
            }

            $builds{$build}->{'status'}=$status;
            $builds{$build}->{'build_number'}=$buildnumber;
            $builds{$build}->{'link'}=$link;
            $builds{$build}->{'built'}=$built;

        }
    }
    #print Dumper(\%builds);


}

#generates a string suitable for displaying build status in skype
sub formatBuildMessage
{
    my ($build)=@_;
    my $status=$builds{$build}->{'status'};

    my $smiley=":)";
    if ($status=~m/broken/)
    {
        $smiley="(devil)";

    }
    elsif ($status=~m/\?/)
    {
         #this means the build is being retested, we should skip it
         $smiley=":|";
    }

    my $message='';

    if ($builds{$build}->{'in_progress'})
    {
       $message=":| $build - rebuild in progress..."
    }
    else
    {

        my ($y,$mon,$d,$h,$m,$s) = $builds{$build}->{'built'} =~ m/(\d\d\d\d)-(\d\d)-(\d\d)T(\d\d):(\d\d):(\d\d)Z/;
        my $time = timegm($s,$m,$h,$d,$mon-1,$y);
        my $age=time()-$time;

        my $mins=round($age/60);
        my $hrs=round($age/3600);
        my $days=round($age/86400);

        my $niceage;
        if ($mins<=2)
        {
            $niceage="a few moments ago";
        }
        elsif ($mins<120)
        {
             $niceage="$mins minutes ago";
        }
        elsif ($hrs<48)
        {
             $niceage="$hrs hours ago";
        }
        else
        {
            $niceage="$days days ago";
        }

        $message="$smiley $build last built $niceage $status";
    }
    return $message;
}

#forget any changes we've flagged
sub resetJenkinsChangeFlags
{
   foreach my $build (keys(%builds))
   {
       $builds{$build}->{'changed'}=0;
   }
}

#checks for builds which have changed state. Can be called
#often, it will only kick in if 60 seconds have elapsed since
#last check
sub checkForJenkinsChanges
{
    my $now=time();
    if (($now-$lastJenkinsCheck) < 60)
    {
        #no need, we fetched it recently
        return;
    }

    checkJenkins();

    my $message='';

    foreach my $build (keys(%builds))
    {
        if ($builds{$build}->{'changed'})
        {
            $builds{$build}->{'changed'}=0;
            $message.=formatBuildMessage($build)."\n";
        }

    }

    if (length($message))
    {
        foreach my $chatId (keys(%$alertRooms))
        {
            chatmessage($chatId, $message);
        }
    }

    $lastJenkinsCheck=$now;
}

#adds or removes a room from the alerts
sub addChatroomToAlerts
{
    my($chatId, $add)=@_;
    if ($add)
    {
        if (exists($alertRooms->{$chatId}))
        {
            chatmessage($chatId, "/me says this room is already getting alerts");
        }
        else
        {
            $alertRooms->{$chatId}=1;
            chatmessage($chatId, "/me added this chatroom to jenkins alerts");
        }
    }
    else
    {
        delete($alertRooms->{$chatId});
        chatmessage($chatId, "/me removed this chatroom from jenkins alerts");
    }

    store $alertRooms, $alertRoomsFile;
}   

sub loadAlertRooms
{
    if (-e  $alertRoomsFile)
    {
        $alertRooms = retrieve( $alertRoomsFile);
    }
}


# Skype event handler
sub onEvent {
    my $skype = shift;
    my $msg = shift;
    #my $command = $skype->create_command( { string => "GET USERSTATUS"}  );
    #print $skype->send_command($command) , "\n";

    #print "handler: $msg\n";

    #an inbound chat message is either
    #MESSAGE 13021257 STATUS RECEIVED (from others)
    #MESSAGE 13021257 STATUS SENT (from us)

    if ($msg =~ m/MESSAGE (\d+) STATUS (SEND|RECEIVED)/)
    {
        my $msgId=$1;

        #get message body
        my $commandstr="GET CHATMESSAGE $msgId BODY";
        my $command = $skype->create_command( { string => $commandstr}  );
        my $output=$skype->send_command($command);


        #if its a message for us...
        if ($output =~ m/MESSAGE $msgId BODY \*([^\s]*)\s*(.*)/i)
        {
            my $botcmd=$1;
            my $botargs=$2;

            $commandstr="GET CHATMESSAGE $msgId CHATNAME";
            $command = $skype->create_command( { string => $commandstr}  );
            $output=$skype->send_command($command);

            if ($output =~ m/MESSAGE $msgId CHATNAME (.*)/)
            {
                my $chatId=$1;
                if (exists($commands{$botcmd}))
                {
                    $commands{$botcmd}->($chatId, $botargs);
                }
                else
                {
                    chatmessage($chatId, "/me suggests trying *help as the robot didn't understand *$botcmd");
                }    
            }
        }
    }
}



#skype idle handler
#Note - SkypeAPI.pm was modified to support this
sub onIdle {
    my $skype = shift;
    checkForJenkinsChanges();
    sleep 0.1;             
}

步骤3-运行机器人

如果您将其保存为robot.pl,只需打开控制台窗口,然后输入perl robot.pl即可运行。Skype会询问您是否允许perl.exe与其通信,确认后即可开始!

进入团队聊天室,输入*jenkins以获取最新构建的摘要,并使用*alert注册该房间以获得构建更改的警报。

完美 :)


自从我写了这篇文章后,我添加了一个很好的*blame命令,您可以使用它来获取有关谁可能会对构建中断负责的良好摘要。如果有足够多的人感兴趣,我将对其进行清理并放在github上... - Paul Dixon
@PaulDixon 如果您能建立一个GitHub项目,我对此很感兴趣。 - Monomachus
这是6年前的事了,我已经不再使用它了。我不知道它是否仍然有效,但我会看看能否找到它... - Paul Dixon

1
尽管上面提供的答案是可行的解决方案,但我认为它有点过时,在提问时没有可用的工具。有一个插件可用于Jenkins与Skype集成:https://wiki.jenkins-ci.org/display/JENKINS/Skype+Plugin 此插件可用于向特定用户或群聊发送通知。我已经使用它一段时间了,效果很好。我主要使用Linux服务器作为我的Jenkins服务器,并有一个专用的Windows服务器安装了Skype并作为机器人用户登录。在从属中运行Skype所需的唯一配置是需要一个名为“skype”的标签。通过此标签,Jenkins将检测从属并将Skype通知路由到该从属。

0

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接