📜 ⬆️ ⬇️

Self-testing system with alerts on Laravel + Bitbucket + HipChat

In this article I will tell you how you can quickly set up an automatic tightening of the new code on the test server of your laravel application, autostart of tests and notification of the result in the corresponding corporate chat. And also catching new bugs in laravel.log

Hi, my name is Dmitry Korets, and I am a PHP developer in a small grocery company.
We use Bitbucket as the hosting code, the team communicates via HipChat, so it will work with them.

As we know, one of the basic requirements for the implementation of continuous integration is the self-testing system. And when the application is not too large, it is not developed by several independent teams, setting up different Bamboo and Jenkins seems rather expensive.

So what do we want?
')
When pushing to the repository, the following should occur:


Moreover:


Let's start!

In the beatback, as in the githaba, there are so-called webhooks (webhooks). This implies sending an HTTP request with certain data in JSON format to the URL you specified for certain changes in the repository.

Go to Settings -> Webhooks -> Add Webhook

image

As you can see, the list is quite large. For now, we are only interested in Push (when the branches merge, the push event will also work), we specify the url we need, save - ready!

Now, when pushing to the repository, the above request will be sent. Even if we have not yet configured the necessary routes in our application, we can see everything from the beatback itself: Settings -> Webhooks -> View requests in front of the hook we created.



Let's go into View details and see, in fact, the object itself that was sent, the response from our server, the status code, the response time, and more.

Adding HipChat Room Integration


Hippat has built-in bitbeat integration. But all that is available there is the connection of notifications about changes in the repository. I was faced with the task of receiving notifications only when a specific branch was updated, since in my company the core of the product is constantly being updated, in parallel with this, new projects are released on the already existing core.

Therefore, at the development stage we have the following structure of branches in the repository with the kernel (conditionally):


The room itself, I think, you know how to create it, then go into it and click Add integration -> Build your own integration, create a name, and chat messages will come on behalf of this integration. We confirm. On the next page we get the most important thing - the URL with the token and immediately an example of the curl command for the test.

curl -d '{"color":"green","message":"My first notification (yey)","notify":false,"message_format":"text"}' -H 'Content-Type: application/json' https://youcompany.hipchat.com/v2/room/{roomId}/notification?auth_token={token}

The hook is configured, the HipChat room is created, we write the logic


Create a route:

 <?php Route::group(['prefix' => LaravelLocalization::setLocale()], function () { Route::group( [ 'prefix' => 'development', ], function () { Route::group(['prefix' => 'bitbucket', 'namespace' => '\BE\Dev\Backend\Http\Controllers'], function () { Route::post('/', [ 'as' => 'bitbucket.push_event', 'uses' => 'BitbucketEventsController@pushEvent' ]); }); } ); } ); 

Controller:

 <?php namespace BE\Dev\Backend\Http\Controllers; use App\Http\Controllers\Controller; use BE\Dev\Services\Bitbucket\BitbucketPushEventService; use BE\Dev\Services\HipChat\HipChatService; use Illuminate\Http\Request; class BitbucketEventsController extends Controller { <?php namespace BE\Dev\Backend\Http\Controllers; use App\Http\Controllers\Controller; use BE\Dev\Services\Bitbucket\BitbucketPushEventService; use BE\Dev\Services\HipChat\HipChatService; use Illuminate\Http\Request; class BitbucketEventsController extends Controller { /** * @var $hipchatService HipChatService */ protected $hipchatService; /** * @var $pushService BitbucketPushEventService */ protected $pushService; protected $config; public function __construct(Request $request) { $this->config = config('be_dev'); $this->hipchatService = app(HipChatService::class); $this->pushService = app(BitbucketPushEventService::class, [$request->all()]); } public function pushEvent(Request $request) { $data = $request->all(); //      staging -    if ($this->pushService->getBranch() != 'staging') { return false; } //   $comment = strtolower($data['push']['changes'][0]['commits'][0]['message']); //    $author = $data['actor']['display_name']; $data = [ 'color' => 'green', 'message' => "<strong>{$author}</strong>      BE   \"{$comment}\"", 'notify' => true, 'message_format' => 'html', ]; //   $this->hipchatService->sendNotification($data); //      no tests -    if (strpos($comment, 'no tests')) { return response()->json([ 'success' => true, 'message' => 'tests was not executed' ])->setStatusCode(200); } // git pull +   +    $service = new \BE\Dev\Services\RuntTestsInQueueAndNotifyHipChatRoom(); $service->handle(); return response()->json([ 'success' => true ])->setStatusCode(200); } } 

Let's see. Here I immediately scattered the service code a bit, so as not to put a lot of logic in the controller. The main part of the action is written in RuntTestsInQueueAndNotifyHipChatRoom.

Content RuntTestsInQueueAndNotifyHipChatRoom:

 <?php namespace BE\Dev\Services; use BE\Dev\Services\HipChat\HipChatService; use Illuminate\Bus\Queueable; use BE\Jobs\Job; use Illuminate\Queue\SerializesModels; use Illuminate\Contracts\Queue\ShouldQueue; class RuntTestsInQueueAndNotifyHipChatRoom extends Job implements ShouldQueue { use Queueable, SerializesModels; /** * @var $deployService DeployService */ protected $deployService; /** * @var $tests RunTests */ protected $tests; /** * @var $hipchatService HipChatService */ protected $hipchatService; public function __construct() { $this->deployService = app(DeployService::class); $this->tests = app(RunTests::class); $this->tests = app(HipChatService::class); } /** * Execute the job. * * @return void */ public function handle() { try { // git pull   $this->deployService->pullPackagesChanges(); //   $outputTests = $this->tests->run(); if ($outputTests === true) { $outputTests = '  '; $colorTests = 'green'; } else { $colorTests = 'red'; } //           $this->hipchatService->sendNotification([ 'color' => $colorTests, 'message' => $outputTests, 'notify' => true, 'message_format' => 'html', ]); } catch (\Exception $exception) { \Log::error($exception->getMessage()); } } } 

How are we going to run the commands on the server in the command line? Laravel uses the Symfony Process component ( documentation ). The important point is, on behalf of which user commands will be run, keep this in mind!

Further the code with comments of our services.

Shrinking changes from the beatback:

 <?php namespace BE\Dev\Services; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; class DeployService { public function pullPackagesChanges() { // cd ../ ,     index.php    public, //        $process = new Process('cd ../ && git pull'); $process->run(); // executes after the command finishes if (!$process->isSuccessful()) { throw new ProcessFailedException($process); } return $process->getOutput(); } } 

Run the tests:

 <?php namespace BE\Dev\Services; use Symfony\Component\Process\Exception\ProcessFailedException; use Symfony\Component\Process\Process; class RunTests { /** * @return string */ public function run() { $process = new Process('cd ../ && vendor/bin/phpunit --tap'); $process->run(); // executes after the command finishes if (!$process->isSuccessful()) { return $process->getIncrementalOutput(); } return true; } } 

Sending notifications is done using a regular curl request, using json_encode () on the data array

 <?php namespace BE\Dev\Services\HipChat; class HipChatService { /** * @var $config array Service config */ protected $config; public function __construct() { $this->config = config('be_dev.hipchat'); } public function sendNotification($data) { // create curl resource $ch = curl_init(); // set url curl_setopt($ch, CURLOPT_URL, $this->config['url']); curl_setopt($ch, CURLOPT_HTTPHEADER, ['Content-Type: application/json']); //return the transfer as a string curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); // $output contains the output string $output = curl_exec($ch); // close curl resource to free up system resources curl_close($ch); return $output; } } 

An example of what should get:



And finally, add another event.

 Log::getMonoLog()->pushHandler(new \Monolog\Handler\HipChatHandler( 'AUTH_TOKEN', 'ROOM_ID', 'hipchat-app', true, \Monolog\Logger::CRITICAL, true, true, 'text', 'COMPANY_NAME'.hipchat.com, 'v2' )); 

Now, each time a CRITICAL message is added to laravel.log, we will send a message to our room. This code must be placed in one of your service providers. Let me remind you that there are such types of messages in the logs:

  1. debug
  2. info
  3. notice
  4. warning
  5. error
  6. critical
  7. alert
  8. emergency

Conclusion


I have it all. Please forgive me for the possible poorly structured information, my first post. The code is available here . In the form of a separate composer package did not make out, laziness took over me. Also, you can add additional logic. For example, if you actively use Atlassian products, in addition to the beats and hippats, there is also a gir, then you can add the ability to automatically close a task in dir and translate it for review to testers if the commit text contains a task code in dir. Or, if a git pull project is not enough for a project, you need to publish configs, rebuild the database, fill it with initial data, etc., then you can write a bash script for the project deployment and run it on the server if the commit text contains a certain substring.

Thanks for attention!

Source: https://habr.com/ru/post/341724/


All Articles