Skip to content

Commit

Permalink
(ans-group#22) Add health-check:health command
Browse files Browse the repository at this point in the history
  • Loading branch information
nick322 committed Jul 14, 2022
1 parent c6f79fd commit 192c662
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 25 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,13 @@ If you'd like to tweak the config file (helpful for configuring the `EnvHealthCh
php artisan vendor:publish --provider="UKFast\HealthCheck\HealthCheckServiceProvider" --tag="config"
```

##### Console command

Check all: `php artisan health-check:status`

Only specific checks: `php artisan health-check:status --only=log,cache`

Except specific checks: `php artisan health-check:status --except=cache`

##### Middleware

Expand Down
54 changes: 54 additions & 0 deletions src/Commands/StatusCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
<?php

namespace UKFast\HealthCheck\Commands;

use Illuminate\Console\Command;
use UKFast\HealthCheck\Facade\HealthCheck;

class StatusCommand extends Command
{
protected $signature = '
health-check:status
{--only= : comma separated checks names to run}
{--except= : comma separated checks names to skip}
';

protected $description = 'Check health status';

public function handle()
{
$only = (string)$this->option('only');
$except = (string)$this->option('except');

if ($only && $except) {
$this->error('Pass --only OR --except, but not both!');

return 1;
}

$onlyChecks = array_map('trim', explode(',', $only));
$exceptChecks = array_map('trim', explode(',', $except));

$problems = [];
/** @var \UKFast\HealthCheck\HealthCheck $check */
foreach (HealthCheck::all() as $check) {
if ($only && !in_array($check->name(), $onlyChecks)) {
continue;
} elseif ($except && in_array($check->name(), $exceptChecks)) {
continue;
}

$status = $check->status();

if ($status->isProblem()) {
$problems[] = [$check->name(), $status->name(), $status->message()];
}
}

if (!$isOkay = empty($problems)) {
$this->table(['name', 'status', 'message'], $problems);
}

return $isOkay ? 0 : 1;
}
}
45 changes: 20 additions & 25 deletions src/Controllers/HealthCheckController.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,46 +2,41 @@

namespace UKFast\HealthCheck\Controllers;

use Illuminate\Contracts\Container\Container;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Illuminate\Support\Arr;
use UKFast\HealthCheck\Status;
use Illuminate\Contracts\Container\Container;

class HealthCheckController
{
public function __invoke(Container $container)
{
$checks = new Collection;
foreach (config('healthcheck.checks') as $check) {
$checks->push($container->make($check));
}

$statuses = $checks->map(function ($check) {
return $check->status();
});

$isProblem = $statuses->contains(function ($status) {
return $status->isProblem();
});
Arr::set($body, 'status', Status::OKAY);

$isDegraded = $statuses->contains(function ($status) {
return $status->isDegraded();
});
$hasProblem = false;

$body = ['status' => ($isProblem ? Status::PROBLEM : ($isDegraded ? Status::DEGRADED : Status::OKAY))];
foreach ($statuses as $status) {
$body[$status->name()] = [];
$body[$status->name()]['status'] = $status->getStatus();
foreach (config('healthcheck.checks') as $check) {
$status = $container->make($check)->status();

Arr::set($body, $status->name() . '.status', $status->getStatus());
if (!$status->isOkay()) {
$body[$status->name()]['message'] = $status->message();
Arr::set($body, $status->name() . '.message', $status->message());
}

if (!empty($status->context())) {
$body[$status->name()]['context'] = $status->context();
Arr::set($body, $status->name() . '.context', $status->context());
}

if ($status->getStatus() == Status::PROBLEM && $hasProblem == false) {
$hasProblem = true;
Arr::set($body, 'status', Status::PROBLEM);
}

if ($status->getStatus() == Status::DEGRADED && $hasProblem == false) {
Arr::set($body, 'status', Status::DEGRADED);
}
}

return new Response($body, $isProblem ? 500 : 200);
return response()
->json($body, in_array(Arr::get($body, 'status'), [Status::DEGRADED, Status::OKAY]) ? 200 : 500);
}
}
2 changes: 2 additions & 0 deletions src/HealthCheckServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
namespace UKFast\HealthCheck;

use Illuminate\Support\ServiceProvider;
use UKFast\HealthCheck\Commands\StatusCommand;
use UKFast\HealthCheck\Commands\CacheSchedulerRunning;
use UKFast\HealthCheck\Commands\HealthCheckMakeCommand;
use UKFast\HealthCheck\Controllers\HealthCheckController;
Expand Down Expand Up @@ -33,6 +34,7 @@ public function boot()
$this->commands([
CacheSchedulerRunning::class,
HealthCheckMakeCommand::class,
StatusCommand::class,
]);
}

Expand Down
128 changes: 128 additions & 0 deletions tests/Commands/StatusCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<?php

namespace Tests\Commands;

use Illuminate\Testing\PendingCommand;
use Mockery;
use Mockery\MockInterface;
use Tests\TestCase;
use UKFast\HealthCheck\Checks\DatabaseHealthCheck;
use UKFast\HealthCheck\Checks\LogHealthCheck;
use UKFast\HealthCheck\HealthCheckServiceProvider;
use UKFast\HealthCheck\Status;

class StatusCommandTest extends TestCase
{
/**
* @test
*/
public function running_command_status()
{
$this->app->register(HealthCheckServiceProvider::class);
config(['healthcheck.checks' => [LogHealthCheck::class]]);

$status = new Status();
$status->okay();
$this->mockLogHealthCheck($status);

$result = $this->artisan('health-check:status');

if ($result instanceof PendingCommand) {
$result->assertExitCode(0);
} else {
$this->assertTrue(true);
}
}

/**
* @test
*/
public function running_command_status_with_only_option()
{
$this->app->register(HealthCheckServiceProvider::class);

$status = new Status();
$status->okay();
$this->mockLogHealthCheck($status);

$result = $this->artisan('health-check:status', ['--only' => 'log']);

if ($result instanceof PendingCommand) {
$result->assertExitCode(0);
} else {
$this->assertTrue(true);
}
}

/**
* @test
*/
public function running_command_status_with_except_option()
{
$this->app->register(HealthCheckServiceProvider::class);
config(['healthcheck.checks' => [LogHealthCheck::class, DatabaseHealthCheck::class]]);

$status = new Status();
$status->okay();
$this->mockLogHealthCheck($status);

$result = $this->artisan('health-check:status', ['--except' => 'database']);

if ($result instanceof PendingCommand) {
$result->assertExitCode(0);
} else {
$this->assertTrue(true);
}
}

/**
* @test
*/
public function running_command_status_with_only_and_except_option()
{
$this->app->register(HealthCheckServiceProvider::class);

$result = $this->artisan('health-check:status', ['--only' => 'log', '--except' => 'log']);

if ($result instanceof PendingCommand) {
$result
->assertExitCode(1)
->expectsOutput('Pass --only OR --except, but not both!');
} else {
$this->assertTrue(true);
}
}

/**
* @test
*/
public function running_command_status_with_failure_condition()
{
$this->app->register(HealthCheckServiceProvider::class);
config(['healthcheck.checks' => [LogHealthCheck::class]]);
$status = new Status();
$status->withName('statusName')->problem('statusMessage');
$this->mockLogHealthCheck($status);

$result = $this->artisan('health-check:status');

if ($result instanceof PendingCommand) {
$result->assertExitCode(1);
//for laravel 5.*
if (method_exists($result, 'expectsTable')) {
$result->expectsTable(['name', 'status', 'message'], [['log', 'statusName', 'statusMessage']]);
}
}
}

private function mockLogHealthCheck(Status $status)
{
$this->instance(
LogHealthCheck::class,
Mockery::mock(LogHealthCheck::class, function (MockInterface $mock) use ($status) {
$mock->shouldReceive('name')->andReturn('log');
$mock->shouldReceive('status')->andReturn($status);
})
);
}
}
19 changes: 19 additions & 0 deletions tests/Controllers/HealthCheckControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -202,6 +202,25 @@ public function returns_status_of_problem_when_both_degraded_and_problem_statuse
'context' => ['debug' => 'info'],
],
], json_decode($response->getContent(), true));

$this->setChecks([AlwaysUpCheck::class, AlwaysDownCheck::class, AlwaysDegradedCheck::class,]);
$response = (new HealthCheckController)->__invoke($this->app);

$this->assertSame([
'status' => 'PROBLEM',
'always-up' => ['status' => 'OK'],
'always-down' => [
'status' => 'PROBLEM',
'message' => 'Something went wrong',
'context' => ['debug' => 'info'],
],
'always-degraded' => [
'status' => 'DEGRADED',
'message' => 'Something went wrong',
'context' => ['debug' => 'info'],
],
], json_decode($response->getContent(), true));

}

protected function setChecks($checks)
Expand Down

0 comments on commit 192c662

Please sign in to comment.