From 192c66253921c921eb6222818133de1e41d15dc3 Mon Sep 17 00:00:00 2001 From: Nick Huang Date: Wed, 13 Jul 2022 17:46:19 +0800 Subject: [PATCH 1/3] (#22) Add health-check:health command --- README.md | 7 + src/Commands/StatusCommand.php | 54 ++++++++ src/Controllers/HealthCheckController.php | 45 +++--- src/HealthCheckServiceProvider.php | 2 + tests/Commands/StatusCommandTest.php | 128 ++++++++++++++++++ .../Controllers/HealthCheckControllerTest.php | 19 +++ 6 files changed, 230 insertions(+), 25 deletions(-) create mode 100644 src/Commands/StatusCommand.php create mode 100644 tests/Commands/StatusCommandTest.php diff --git a/README.md b/README.md index 9637cde..5c57368 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/src/Commands/StatusCommand.php b/src/Commands/StatusCommand.php new file mode 100644 index 0000000..7f7451b --- /dev/null +++ b/src/Commands/StatusCommand.php @@ -0,0 +1,54 @@ +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; + } +} diff --git a/src/Controllers/HealthCheckController.php b/src/Controllers/HealthCheckController.php index 57fe757..8c2077a 100644 --- a/src/Controllers/HealthCheckController.php +++ b/src/Controllers/HealthCheckController.php @@ -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); } } diff --git a/src/HealthCheckServiceProvider.php b/src/HealthCheckServiceProvider.php index c185795..24b3183 100644 --- a/src/HealthCheckServiceProvider.php +++ b/src/HealthCheckServiceProvider.php @@ -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; @@ -33,6 +34,7 @@ public function boot() $this->commands([ CacheSchedulerRunning::class, HealthCheckMakeCommand::class, + StatusCommand::class, ]); } diff --git a/tests/Commands/StatusCommandTest.php b/tests/Commands/StatusCommandTest.php new file mode 100644 index 0000000..f094f59 --- /dev/null +++ b/tests/Commands/StatusCommandTest.php @@ -0,0 +1,128 @@ +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); + }) + ); + } +} diff --git a/tests/Controllers/HealthCheckControllerTest.php b/tests/Controllers/HealthCheckControllerTest.php index 35e5f35..554ded8 100644 --- a/tests/Controllers/HealthCheckControllerTest.php +++ b/tests/Controllers/HealthCheckControllerTest.php @@ -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) From 4157a0655ea424057097171f12978923804952eb Mon Sep 17 00:00:00 2001 From: Phil Young Date: Mon, 15 Jul 2024 13:32:35 +0100 Subject: [PATCH 2/3] Pull in updates from another branch This line has changed in both locations --- src/Controllers/HealthCheckController.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controllers/HealthCheckController.php b/src/Controllers/HealthCheckController.php index 8c2077a..0b7a07f 100644 --- a/src/Controllers/HealthCheckController.php +++ b/src/Controllers/HealthCheckController.php @@ -37,6 +37,6 @@ public function __invoke(Container $container) } return response() - ->json($body, in_array(Arr::get($body, 'status'), [Status::DEGRADED, Status::OKAY]) ? 200 : 500); + ->json($body, in_array(Arr::get($body, 'status'), [Status::DEGRADED, Status::OKAY]) ? 200 : config('healthcheck.default-problem-http-code', 500)); } } From cfe8890fd6a63fa3fd6de2673b238b34bde41cd6 Mon Sep 17 00:00:00 2001 From: Phil Young Date: Mon, 15 Jul 2024 13:33:12 +0100 Subject: [PATCH 3/3] Add a success message to the check command This now provides visual feedback --- src/Commands/StatusCommand.php | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Commands/StatusCommand.php b/src/Commands/StatusCommand.php index 7f7451b..60e3d69 100644 --- a/src/Commands/StatusCommand.php +++ b/src/Commands/StatusCommand.php @@ -49,6 +49,8 @@ public function handle() $this->table(['name', 'status', 'message'], $problems); } + $this->info('All checks passed successfully'); + return $isOkay ? 0 : 1; } }