211 lines
5.8 KiB
Perl
Executable file
211 lines
5.8 KiB
Perl
Executable file
#!/usr/bin/perl
|
|
#
|
|
# bin/worker/sns-notifier
|
|
#
|
|
# Listens on SNS queues and takes some action when a message comes in.
|
|
#
|
|
# Authors:
|
|
# Mark Smith <mark@dreamwidth.org>
|
|
#
|
|
# Copyright (c) 2019 by Dreamwidth Studios, LLC.
|
|
#
|
|
# This program is free software; you may redistribute it and/or modify it under
|
|
# the same terms as Perl itself. For a copy of the license, please reference
|
|
# 'perldoc perlartistic' or 'perldoc perlgpl'.
|
|
#
|
|
|
|
use strict;
|
|
use v5.10;
|
|
|
|
BEGIN {
|
|
require "$ENV{LJHOME}/cgi-bin/ljlib.pl";
|
|
}
|
|
|
|
use Log::Log4perl;
|
|
my $log = Log::Log4perl->get_logger(__PACKAGE__);
|
|
|
|
use List::Util qw/ sum /;
|
|
use LWP::UserAgent;
|
|
use Paws;
|
|
use POSIX qw/ strftime /;
|
|
|
|
use LJ::JSON;
|
|
|
|
my $HOOK_URL = $LJ::BUILD_NOTIFY_HOOK_URL or die;
|
|
|
|
my @ALL_PHASES = qw/
|
|
SUBMITTED QUEUED PROVISIONING DOWNLOAD_SOURCE INSTALL
|
|
PRE_BUILD POST_BUILD UPLOAD_ARTIFACTS FINALIZING COMPLETED
|
|
/;
|
|
|
|
my %PHASES = (
|
|
SUBMITTED => rgb( 255, 255, 128 ),
|
|
QUEUED => rgb( 128, 128, 255 ),
|
|
BUILD => rgb( 128, 128, 255 ),
|
|
COMPLETED => rgb( 128, 255, 128 ),
|
|
);
|
|
|
|
## Establish SQS connection
|
|
my $paws = Paws->new(
|
|
config => {
|
|
region => $LJ::SQS{region},
|
|
},
|
|
) or $log->logcroak('Failed to initialize Paws object.');
|
|
my $sqs = $paws->service('SQS')
|
|
or $log->logcroak('Failed to initialize Paws::SQS object.');
|
|
my $queue_url = $sqs->GetQueueUrl( QueueName => 'dw-codebuild-notifications' )->QueueUrl;
|
|
|
|
## Start listening for notifications, one by one
|
|
while (1) {
|
|
my $res = eval {
|
|
$sqs->ReceiveMessage(
|
|
QueueUrl => $queue_url,
|
|
MaxNumberOfMessages => 1,
|
|
WaitTimeSeconds => 20
|
|
);
|
|
};
|
|
if ( $@ && $@->isa('Paws::Exception') ) {
|
|
die $@->Message;
|
|
}
|
|
|
|
my $messages = $res->Messages;
|
|
$log->warn( 'Got ', scalar(@$messages), ' messages.' );
|
|
next unless @$messages;
|
|
|
|
foreach my $message (@$messages) {
|
|
my $msg = LJ::JSON::from_json( $message->Body );
|
|
my $d = $msg->{detail};
|
|
my $ai = $d->{'additional-information'};
|
|
|
|
my $project = $d->{'project-name'} // '(unknown)';
|
|
my $current = $d->{'current-phase'};
|
|
my $completed = $d->{'completed-phase'};
|
|
my $next =
|
|
$ai->{phases}
|
|
? $ai->{phases}[-1]{'phase-type'}
|
|
: undef;
|
|
|
|
my ( $phase, $message );
|
|
if ( $current eq 'SUBMITTED' ) {
|
|
|
|
# Job has been submitted (very first phase)
|
|
$phase = $current;
|
|
$message = 'Build request submitted, waiting for a VM...';
|
|
}
|
|
elsif ( $current eq 'COMPLETED' ) {
|
|
|
|
# Job is fully complete
|
|
$phase = $current;
|
|
$message = 'Upload completed, build was successful!';
|
|
}
|
|
elsif ( $completed eq 'BUILD' ) {
|
|
|
|
# Job has finished building, moving on to uploading
|
|
$phase = $completed;
|
|
$message = 'Build stage complete, uploading artifact.';
|
|
}
|
|
elsif ( $completed eq 'QUEUED' ) {
|
|
|
|
# Job has been assigned resources and is beginning to execute
|
|
#
|
|
# This might be nice if we ever see lag between SUBMITTED and QUEUED,
|
|
# but in all our experience so far, it's instant. Let's not send it
|
|
# for now.
|
|
#
|
|
# $phase = $completed;
|
|
# $message = 'Resources allocated, provisioning build environment.';
|
|
}
|
|
next unless $phase;
|
|
|
|
my $color = $PHASES{$phase};
|
|
my $seconds = $d->{'completed-phase-duration-seconds'};
|
|
my $elapsed =
|
|
$ai->{phases}
|
|
? sum( map { $_->{'duration-in-seconds'} + 0 } @{ $ai->{phases} } )
|
|
: undef;
|
|
|
|
my $embed = make_embed(
|
|
$project, $message, $color,
|
|
commit => github_url( $ai->{'source-version'} ),
|
|
duration => ( $seconds ? timestr($seconds) : '--' ),
|
|
elapsed => ( $elapsed ? timestr($elapsed) : '--' ),
|
|
);
|
|
send_webhook($embed) or die;
|
|
}
|
|
|
|
my $idx = 0;
|
|
$sqs->DeleteMessageBatch(
|
|
QueueUrl => $queue_url,
|
|
Entries => [ map { { Id => $idx++, ReceiptHandle => $_->ReceiptHandle } } @$messages ]
|
|
);
|
|
if ( $@ && $@->isa('Paws::Exception') ) {
|
|
die $@->Message;
|
|
}
|
|
}
|
|
|
|
sub make_embed {
|
|
my ( $project, $msg, $color, @fields ) = @_;
|
|
|
|
my @embed_fields;
|
|
while ( my @field = splice @fields, 0, 2 ) {
|
|
push @embed_fields,
|
|
{ name => $field[0], value => $field[1], inline => LJ::JSON->to_boolean(1) };
|
|
}
|
|
|
|
my $obj = {
|
|
'embeds' => [
|
|
{
|
|
'title' => sprintf( '[%s] build update', $project ),
|
|
'description' => $msg,
|
|
'fields' => \@embed_fields,
|
|
'color' => $color,
|
|
'footer' => {
|
|
'text' => 'AWS CodeBuild Notifier'
|
|
},
|
|
'timestamp' => strftime( '%Y-%m-%dT%H:%M:%S.000Z', gmtime ),
|
|
}
|
|
]
|
|
};
|
|
|
|
return $obj;
|
|
}
|
|
|
|
sub send_webhook {
|
|
my $obj = $_[0];
|
|
|
|
my $ua = LWP::UserAgent->new;
|
|
$ua->agent('Dreamwidth AWS CodeBuild Notifier <support@dreamwidth.org>');
|
|
|
|
my $res = $ua->post(
|
|
$HOOK_URL,
|
|
Content => LJ::JSON::to_json($obj),
|
|
'Content-Type' => 'application/json',
|
|
);
|
|
return 1 if $res->is_success;
|
|
|
|
die $res->decoded_content;
|
|
}
|
|
|
|
sub rgb {
|
|
return ( $_[0] << 16 ) + ( $_[1] << 8 ) + $_[2];
|
|
}
|
|
|
|
sub timestr {
|
|
my $secs = $_[0];
|
|
|
|
if ( $secs > 3600 ) {
|
|
return sprintf( '%dh%s', int( $secs / 3600 ), timestr( $secs % 3600 ) );
|
|
}
|
|
elsif ( $secs > 60 ) {
|
|
return sprintf( '%dm%s', int( $secs / 60 ), timestr( $secs % 60 ) );
|
|
}
|
|
else {
|
|
return sprintf( '%ds', $secs );
|
|
}
|
|
}
|
|
|
|
sub github_url {
|
|
my $hash = substr( $_[0], 0, 8 );
|
|
|
|
return qq|[$hash](https://github.com/dreamwidth/dreamwidth/commit/$hash)|;
|
|
}
|