How can I alert Skype chatrooms with Jenkins build status? - api

Our company uses Skype for communications, and I'd like to be able to send alerts to Skype chatrooms when a Jenkins build fails (and when it recovers too).
How can I do this?

I've done this using the Skype Public API
What I did was write a Perl script which uses the SkypeAPI CPAN module to handle the communications with Skype. It's a little clunky, as the script needs to run on a desktop which is running Skype. I run it on my own desktop which is always on anyway, but this does mean the bot appears to be 'me' to the rest of our team.
The end result is fantastic - whenever a jenkins build changes state, the bot sends a message to any Skype chats which have registered an interest by typing *alert. Additionally, any developer can see and share the latest build status by typing *jenkins
Step 1 - Extending the SkypeAPI module
Now, the SkypeAPI module is pretty basic. It has a message loop in the listen() method which checks for new events from the Skype client, and sleeps for a moment if there none.
I wanted my script to hook into this loop so that I could have my bot periodically check Jenkins RSS feed, so I made the following modifications to SkypeAPI.pm after I had installed it with the ActiveState package manager:
I declared new property 'idler' along with the existing properties...
__PACKAGE__->mk_accessors(
qw/api handler_list stop_listen idler/
);
I added a method to set an 'idler' callback which the module will call instead of sleeping
sub register_idler {
my $self = shift;
my $ref_sub = shift;
$self->idler($ref_sub);
}
Finally I modified the message loop to call the idler if set
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);
}
}
}
Step 2 - write a robot script
Now the module is a little more capable, it's just a matter of writing a script to act as a bot. Here's mine - I've made a few edits from my original as it contained other irrelevant functionality, but it should give you a starting point.
All of the dependant modules can be installed with the ActiveState package manager.
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;
}
Step 3 - Run the bot
If you've saved this as robot.pl, just open a console window and perl robot.pl should get it running. Skype will ask you if perl.exe should be allowed to communicate with it, and once you confirm that, you're good to go!
Go into a team chatroom and type *jenkins for an summary of latest builds, and register the room for alerts of build changes with *alert
Perfect :)

Though the answer provided above is a working solution, I think that is a bit old and no tools were available at the time the question was asked. There is a plugin available for Jenkins to integrate with skype:https://wiki.jenkins-ci.org/display/JENKINS/Skype+Plugin
This plugin can be used to send notification to a particular user or a group chat. I've been using it for a while and it works great. I primarily use linux servers for my Jenkins servers and I have a dedicated windows server with skype installed and logged in as a bot user. The only configuration needed for running skype in a slave is that it requires a label called "skype". With this label Jenkins will detect the slave and route skype notification to that slave.

One can use Sevabot (https://sevabot-skype-bot.readthedocs.org/) project to setup a HTTP based interface for Skype and then use Jenkin's Notification Plugin:
Example: https://sevabot-skype-bot.readthedocs.org/en/latest/jenkins.html

Related

Perl6: Cannot invoke this object (REPR: P6opaque; Parallel::ForkManager)

I'm attempting to run a series of shell commands in parallel in Perl6, using Perl5's Parallel::ForkManager
This is an almost exact translation of working Perl5 code.
CONTROL {
when CX::Warn {
note $_;
exit 1;
}
}
use fatal;
role KeyRequired {
method AT-KEY (\key) {
die "Key {key} not found" unless self.EXISTS-KEY(key);
nextsame;
}
}
use Parallel::ForkManager:from<Perl5>;
sub run_parallel (#cmd) {
my $manager = Parallel::ForkManager(8).new();
for (#cmd) -> $command {
$manager.start and $manager.next;
my $proc = shell $command, :out, :err;
if $proc.exitcode != 0 {
put "$command failed";
put $proc.out.slurp;
put $proc.err.slurp;
die;
}
$manager.finish;
}
$manager.wait_all_children;#necessary after all lists
}
my #cmd;
my Str $dir = 'A/1';
for dir($dir, test => /\.vcf\.gz$/) -> $vcf {
#cmd.append: "aws s3 cp $vcf s3://s3dir/$dir/"
}
put #cmd.elems;
run_parallel(#cmd);
Basically, I'm trying to parallelize tedious shell commands.
However, this mysterious error comes up:
Cannot invoke this object (REPR: P6opaque; Parallel::ForkManager) in
sub run_parallel at 2.aws_cp.p6 line 18 in block at
2.aws_cp.p6 line 39
Why is Perl6 saying this? what is wrong? how can I get these commands to run?
Perhaps there is a more native/idiomatic way to run shell commands in parallel in Perl6?
You probably want to look at using Proc::Async which runs external commands asynchronously in threads without forking separate instances of the code to do it.
Perl5's Parallel::ForkManager probably won't work in Perl6 because of how Inline::Perl5 is implemented.
Inline::Perl5 embeds the Perl5 compiler/runtime inside of Perl6.
Parallel::ForkManager expects that Perl5 was run by itself.
If you ever did get it to do something other than generate an error it would probably screw up the Perl6 runtime. The main problem is the use of fork. For more information about why fork is a a problem see the article Bart Wiegmans (brrt) wrote about it: “A future for fork(2)”
Perl6 already has a similar feature that is easier to use.
sub run_parallel (#cmd) {
my #children = do for (#cmd) -> $command {
start {
my $proc = shell $command, :out, :err;
if $proc.exitcode != 0 {
put "$command failed";
put $proc.out.slurp;
put $proc.err.slurp;
die;
}
}
}
await #children;
}
start is a prefix that tells the runtime to start running the following code sometime in the near future. It returns a Promise.
await takes a list of Promises and returns a list of their results.
start basically calls Promise.start which is similar to:
sub start ( &code ) {
my $promise = Promise.new;
my $vow = $promise.vow;
$*SCHEDULER.cue(
{ $vow.keep(code(|c)) },
:catch(-> $ex { $vow.break($ex); }) );
$promise
}
So it will use the globally available thread pool in $*SCHEDULER. If you want to use a separate one you could.
sub run_parallel (#cmd) {
my $*SCHEDULER = ThreadPoolScheduler.new(max_threads => 8);
my #children = do for (#cmd) -> $command {
start {
my $proc = shell $command, :out, :err;
if $proc.exitcode != 0 {
put "$command failed";
put $proc.out.slurp;
put $proc.err.slurp;
die;
}
}
}
await #children;
}
It would make more sense to use Proc::Async for this though.

BigQuery php client library queryresult shows isComplete false. But I can confirm the query is successful

$queryResults = $this->bigQuery->runQuery($query, ['parameters' => ['id' => $id]]);
$info = $queryResults->info();
var_dump($info);
// var_dump($queryResults);
$isComplete = $queryResults->isComplete();
if ($isComplete) {
exit("Insert. Done!");
}
The query is a "insert select" statement. which I had run successful on bigquery cloud console directly.
When I use php client library here, the query finished successful too and I can confirm that it is the same as I run query directly on google cloud console.
But the info of the query shows isComplete false.
array(3) {
["kind"]=>
string(22) "bigquery#queryResponse"
["jobReference"]=>
array(2) {
["projectId"]=>
string(26) "myprojectid"
["jobId"]=>
string(31) "job_OHcckVSSwAI7pHXijmmUqK5H4XE"
}
["jobComplete"]=>
bool(false)
}
There are no errors report form the $queryResults->info();. How could I find out why it shows me isComplete false?
Probably you still need to run $queryResults -> reload() to update the job status. As in the docs:
$isComplete = $queryResults->isComplete();
while (!$isComplete) {
sleep(1); // small delay between requests
$queryResults->reload();
$isComplete = $queryResults->isComplete();
}

PHP InstanceOf works locally but not on host server

I have an issue with PHP 7's instanceof statement that is only happening on certain conditions.
It seems that instanceof works locally on my dev machine (MAMP Pro running PHP 7.0.13) but not on my Hosted Server (HostEurope, PHP 7).
I have tried the following :
downgrading to PHP 5.6
using is_a instead
Using fully qualified name e.g. \Site\Ad
but they all exhibit the same behaviour.
I've tried Googling "PHP instanceof not working" and variations of it but I haven't found anything relevant.
I was wondering if anyone had experienced something similar or possible solutions to try?
The Code in question is:
<?php
namespace Site;
require_once(__DIR__."/../interface/IOutput.php");
require_once(__DIR__."/../lib/Lib.php");
require_once(__DIR__."/../site/AdMediumRectangle.php");
require_once(__DIR__."/../site/AdSnapBanner.php");
require_once(__DIR__."/../const/Const.php");
class AdFactory
{
/**
* Define(AD_BANNER, 0);
* Define(AD_RECTANGE, 1);
* Define(AD_SUPERBANNER, 2);
* Define(AD_SKYSCRAPER, 3);
**/
/**
* #param $object
* #return AdMediumRectangle|AdSnapBanner|string
*/
public static function CreateObject($object)
{
$ad = wire(pages)->get("/ads/")->children->getRandom();
if ($ad == null)
return new \Exception("No Random Ad found");
switch ($object) {
case AD_BANNER:
echo "AD_Banner Selected\r\n";
$adSnapBanner = new AdSnapBanner($ad);
return $adSnapBanner;
break;
case AD_RECTANGLE:
echo "AD Rectangle Created\r\n";
$adRectangle = new AdMediumRectangle($ad);
return $adRectangle;
break;
case AD_SUPERBANNER:
case AD_SKYSCRAPER:
default:
echo "AdFactory BlankObject created";
return "";
break;
}
}
public static function Markup($object)
{
$obj = AdFactory::CreateObject($object);
if (($obj instanceof AdSnapBanner) || ($obj instanceof AdMediumRectangle)) {
echo "InstanceOf worked";
return $obj->Markup();
}
else {
echo "return blankString";
return "";
}
}
}
Update : This is the code that calls the above AdFactory class
<?php
namespace Site;
require_once(__DIR__."/../interface/IOutput.php");
require_once(__DIR__."/../lib/Lib.php");
require_once(__DIR__."/../factory/AdFactory.php");
require_once (__DIR__."/../const/Const.php");
class AdInjector
{
public static function Inject($page, $ad_pos)
{
//Select an Ad from /Ads/ according to criteria
//$ads = wire(pages)->get("/ads/")->children;
$count = 1; //$ads->count();
if ($count > 0) {
$mod = $page->id % 3;
echo "mod=" . $mod . "\r\n";
if ($mod == $ad_pos) {
switch ($mod) {
case AD_POS_TITLE;
case AD_POS_BANNER:
//Pick an Snap Banner
echo "Banner Injected (banner):" . AD_BANNER . "\r\n";
return AdFactory::Markup(AD_BANNER);
break;
case AD_POS_SIBLINGS:
echo "Banner Injected (rect):" . AD_RECTANGLE . "\r\n";
//Pick an Ad Rectangle
return AdFactory::Markup(AD_RECTANGLE);
break;
default:
return "";
break;
}
} else
return "";
} else
return "";
}
}
instanceof is a language construct which is so essential to PHP that it is de facto impossible not to work properly.
The code you provided is not enough to tell where the issue might be happening.
Chances are, you have a folder not readable on your online server and simply get somewhere a null value instead of an expected object along your code. Ask yourself: "If it is not the object I expect, what else is it?"
Use var_dump() or printf() to investigate what your variables actually contain and you will find the error soon.
For your code, PHPUnit tests would be a benefit, or at least the use of assert() here and there in your code.
Turns out there was a bug in 1 of the API calls I was making to the Processwire CMS.
$ad = wire(pages)->get("/ads/")->children->getRandom();
And my local and server instance of Processwire was not the same version, which was news to me. I normally have it synchronised, including any modules I use.
I also suspect my null check is not correct PHP, to add to the problem.
It has to do with namespaces used in the code:
Locally (Code with no namespaces) I used this, working fine:
if ($xmlornot instanceof SimpleXMLElement) { }
But on the server (code with namespaces) only this worked:
if ($xmlornot instanceof \SimpleXMLElement) { }
See also this question/answer: instanceof operator returns false for true condition

How to insert data in database after payment successful through instamojo

I have integrated instamojo payment api with my website on PHP.
I can also insert data into my database before payment api is called.
Now how can i insert data into my database after my payment is successful !
Thanks
You have to save this code as php file in your host and then set this file URL as web hook URL for Product/Payment link in Instamojo. You can also check whether this works on Web hook checking page in Instamojo.
<?php
/*
Basic PHP script to handle Instamojo RAP webhook.
*/
$data = $_POST;
$mac_provided = $data['mac']; // Get the MAC from the POST data
unset($data['mac']); // Remove the MAC key from the data.
$ver = explode('.', phpversion());
$major = (int) $ver[0];
$minor = (int) $ver[1];
if($major >= 5 and $minor >= 4){
ksort($data, SORT_STRING | SORT_FLAG_CASE);
}
else{
uksort($data, 'strcasecmp');
}
// You can get the 'salt' from Instamojo's developers page(make sure to log in first): https://www.instamojo.com/developers
// Pass the 'salt' without <>
$mac_calculated = hash_hmac("sha1", implode("|", $data), "<YOUR_SALT>");
if($mac_provided == $mac_calculated){
if($data['status'] == "Credit"){
// Payment was successful, mark it as successful in your database.
// You can acess payment_request_id, purpose etc here.
}
else{
// Payment was unsuccessful, mark it as failed in your database.
// You can acess payment_request_id, purpose etc here.
}
}
else{
echo "MAC mismatch";
}
?>

CloudSQL persitent connections error

I'm using Google Cloud SQL 2nd generations instances and persistent connections using PHP PDO driver but, from time to time, and just in some App Engine instances, the pooled connections get corrupted and the connections begin to fail, sending an ugly message to the user.
I tried to solve this trying to make new connnections, even disabling persistency, but it didn't work:
for ($attempt = 1; !$this->link; $attempt++) {
try {
if ($attempt > $persistent / 2) {
unset($options[PDO::ATTR_PERSISTENT]);
}
$this->link = new PDO($dsn_string, $user, $pass, $options);
} catch (PDOException $err) {
if ($attempt <= $persistent) {
usleep($attempt * 100000);
} else {
throw new DB_Exception("Error connecting database (after $attempt attempts):\n" . $err->getMessage(), $err->getCode(), null, $err);
}
}
}