package Ukigumo::Client; use strict; use warnings; use 5.008001; our $VERSION = '0.07'; use Carp (); use Capture::Tiny; use Encode::Locale; use Encode; use File::Spec; use File::Path qw(mkpath); use LWP::UserAgent; use Time::Piece; use English '-no_match_vars'; use File::Basename qw(dirname); use HTTP::Request::Common qw(POST); use JSON qw(decode_json); use File::Temp; use File::HomeDir; use URI::Escape qw(uri_escape); use Ukigumo::Constants; use Mouse; has 'workdir' => ( is => 'ro', isa => 'Str', required => 1, lazy => 1, default => sub { my $self = shift; File::Spec->catdir( File::HomeDir->my_home, '.ukigumo', 'work') }, ); has 'project' => ( is => 'ro', isa => 'Str', default => sub { my $self = shift; my $proj = $self->repository; $proj =~ s/\.git$//; $proj =~ s!.+\/!!; $proj || '-'; }, lazy => 1, ); has 'logfh' => ( is => 'ro', lazy => 1, default => sub { File::Temp->new(UNLINK => 1) } ); has 'server_url' => ( is => 'ro', isa => 'Str', required => 1, ); has 'user_agent' => ( is => 'ro', required => 1, lazy => 1, default => sub { my $ua = LWP::UserAgent->new( agent => "ukigumo-client/$Ukigumo::Client::VERSION" ); $ua->env_proxy; $ua; }, ); has quiet => ( is => 'ro', isa => 'Bool', default => 0, ); # components has 'vc' => ( is => 'ro', required => 1, handles => [qw(get_revision branch repository)], ); has 'executor' => ( is => 'ro', required => 1, ); has 'notifiers' => ( is => 'rw', default => sub { +[ ] }, ); sub push_notifier { my $self = shift; push @{$self->notifiers}, @_; } sub run { my $self = shift; my $workdir = File::Spec->catdir( $self->workdir, uri_escape($self->project), uri_escape($self->branch) ); $self->log("ukigumo-client $VERSION"); $self->log("start testing : " . $self->vc->description()); $self->log("working directory : " . $workdir); { mkpath($workdir); chdir($workdir) or die "Cannot chdir(@{[ $workdir ]}): $!"; $self->log('run vc : ' . ref $self->vc); my $orig_revision = $self->vc->get_revision(); $self->vc->update($self, $workdir); my $current_revision = $self->vc->get_revision(); my $vc_log = $self->vc->get_log($orig_revision, $current_revision); $self->log('run executor : ' . ref $self->executor); my $status = $self->executor->run($self); $self->log('finished testing : ' . $status); my ($report_url, $last_status) = $self->send_to_server($status, $current_revision, $vc_log); $self->log("sending notification: @{[ $self->branch ]}, $status"); for my $notify (@{$self->notifiers}) { $notify->send($self, $status, $last_status, $report_url); } } $self->log("end testing"); } sub send_to_server { my ($self, $status, $current_revision, $vc_log) = @_; my $ua = $self->user_agent(); # flush log file before send it $self->logfh->flush(); my $req = POST $self->server_url . '/api/v1/report/add', Content_Type => 'form-data', Content => [ project => $self->project, branch => $self->branch, repo => $self->repository, revision => $current_revision, status => $status, vc_log => $vc_log, body => [$self->logfh->filename], ]; my $res = $ua->request($req); $res->is_success or die $res->as_string; my $dat = eval { decode_json($res->decoded_content) } || $res->decoded_content . " : $@"; $self->log("report url: $dat->{report}->{url}"); my $report_url = $dat->{report}->{url} or die "Cannot get report url"; return ($report_url, $dat->{report}->{last_status}); } sub tee { my ($self, $command) = @_; $self->log("command: $command"); my ($out) = Capture::Tiny::capture_merged { ( $EUID, $EGID ) = ( $UID, $GID ); system $command }; Encode::decode("console_out", $out); print $out; print {$self->logfh} $out; return $?; } sub log { my $self = shift; my $msg = join( ' ', Time::Piece->new()->strftime('[%Y-%m-%d %H:%M]'), '[' . $self->branch . ']', @_ ) . "\n"; print STDOUT $msg unless $self->quiet; print {$self->logfh} $msg; } 1; __END__ =encoding utf8 =head1 NAME Ukigumo::Client - Client library for Ukigumo =head1 SYNOPSIS use Ukigumo::Client; use Ukigumo::Client::VC::Git; use Ukigumo::Client::Executor::Auto; use Ukigumo::Client::Notify::Debug; use Ukigumo::Client::Notify::Ikachan; my $app = Ukigumo::Client->new( vc => Ukigumo::Client::VC::Git->new( branch => $branch, repository => $repo, ), executor => Ukigumo::Client::Executor::Perl->new(), server_url => $server_url, project => $project, ); $app->push_notifier( Ukigumo::Client::Notify::Ikachan->new( url => $ikachan_url, channel => $ikachan_channel, ) ); $app->run(); =head1 DESCRIPTION Ukigumo::Client is client library for Ukigumo. =head1 ATTRIBUTES =over 4 =item workdir Working directory for the code. It's $ENV{HOME}/.ukigumo/work/$project/$branch by default. =item project Its' project name. This is a mandatory parameter. =item logfh Log file handle. It's read only parameter. =item server_url URL of the Ukigumo server. It's required. =item user_agent instance of L<LWP::UserAgent>. It's have a default value. =item vc This is a version controller object. It's normally Ukigumo::Client::VC::*. But you can write your own class. VC::* objects should have a following methods: get_revision branch repository =item executor This is a test executor object. It's normally Ukigumo::Client::Executor::*. But you can write your own class. =item notifiers This is a arrayref of notifier object. It's normally Ukigumo::Client::Notify::*. But you can write your own class. =back =head1 METHODS =over 4 =item $client->push_notifier($notifier : Ukigumo::Client::Notify) push a notifier object to $client->notifiers. =item $client->run() Run a test context. =item $client->send_to_server($status: Int) Send a notification to the sever. =item $client->tee($command: Str) This method runs C<< $command >> and tee the output of the STDOUT/STDERR to the logfh. I<Return>: exit code by the C<< $command >>. =item $client->log($message) Print C<< $message >> and write to the logfh. =back =head1 AUTHOR Tokuhiro Matsuno E<lt>tokuhirom AAJKLFJEF@ GMAIL COME<gt> =head1 SEE ALSO L<Ukigumo::Server>, L<Ukigumo:https://github.com/ukigumo/> =head1 LICENSE Copyright (C) Tokuhiro Matsuno This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself. =cut