diff --git a/docs/basic-usage/pausing-and-resuming-checks.md b/docs/basic-usage/pausing-and-resuming-checks.md new file mode 100644 index 00000000..ae52818e --- /dev/null +++ b/docs/basic-usage/pausing-and-resuming-checks.md @@ -0,0 +1,29 @@ +--- +title: Pausing and resuming checks +weight: 5 +--- + +You might want to temporarily pause checks to avoid unnecessary alerts during deployments or maintenance. This is particularly useful when services might experience brief downtime, which could otherwise trigger false alarms. + +## Pause Checks + +You can pause checks for a specified duration. By default, checks will be paused for 300 seconds (5 minutes). + +```bash +# Pause checks for the default duration (300 seconds) +php artisan health:pause + +# Pause checks for a custom duration (in seconds) +php artisan health:pause 60 +``` + +During the pause period, checks will not run, and no alerts will be triggered. + +## Resume Checks + +If you need to resume checks before the pause duration ends, you can do so with the following command: + +```bash +# Resume checks immediately +php artisan health:resume +``` diff --git a/src/Commands/PauseHealthChecksCommand.php b/src/Commands/PauseHealthChecksCommand.php new file mode 100644 index 00000000..9eb929cf --- /dev/null +++ b/src/Commands/PauseHealthChecksCommand.php @@ -0,0 +1,26 @@ +argument('seconds'); + + Cache::put(self::CACHE_KEY, true, $seconds); + + $this->comment('All health check paused until ' . now()->addSeconds($seconds)->toDateTimeString()); + + return self::SUCCESS; + } +} diff --git a/src/Commands/ResumeHealthChecksCommand.php b/src/Commands/ResumeHealthChecksCommand.php new file mode 100644 index 00000000..4b7c5408 --- /dev/null +++ b/src/Commands/ResumeHealthChecksCommand.php @@ -0,0 +1,21 @@ +comment('All health check resumed'); + + return self::SUCCESS; + } +} diff --git a/src/Commands/RunHealthChecksCommand.php b/src/Commands/RunHealthChecksCommand.php index a7d20f92..270e57b7 100644 --- a/src/Commands/RunHealthChecksCommand.php +++ b/src/Commands/RunHealthChecksCommand.php @@ -5,6 +5,7 @@ use Exception; use Illuminate\Console\Command; use Illuminate\Support\Collection; +use Illuminate\Support\Facades\Cache; use Spatie\Health\Checks\Check; use Spatie\Health\Checks\Result; use Spatie\Health\Enums\Status; @@ -25,6 +26,12 @@ class RunHealthChecksCommand extends Command public function handle(): int { + if (Cache::get(PauseHealthChecksCommand::CACHE_KEY)) { + $this->info('Checks paused'); + + return self::SUCCESS; + } + $this->info('Running checks...'); $results = $this->runChecks(); diff --git a/src/HealthServiceProvider.php b/src/HealthServiceProvider.php index b9bf60ad..25d72479 100644 --- a/src/HealthServiceProvider.php +++ b/src/HealthServiceProvider.php @@ -5,6 +5,8 @@ use Illuminate\Support\Facades\Route; use Spatie\Health\Commands\DispatchQueueCheckJobsCommand; use Spatie\Health\Commands\ListHealthChecksCommand; +use Spatie\Health\Commands\PauseHealthChecksCommand; +use Spatie\Health\Commands\ResumeHealthChecksCommand; use Spatie\Health\Commands\RunHealthChecksCommand; use Spatie\Health\Commands\ScheduleCheckHeartbeatCommand; use Spatie\Health\Components\Logo; @@ -33,7 +35,9 @@ public function configurePackage(Package $package): void ListHealthChecksCommand::class, RunHealthChecksCommand::class, ScheduleCheckHeartbeatCommand::class, - DispatchQueueCheckJobsCommand::class + DispatchQueueCheckJobsCommand::class, + PauseHealthChecksCommand::class, + ResumeHealthChecksCommand::class, ); } diff --git a/src/Http/Controllers/SimpleHealthCheckController.php b/src/Http/Controllers/SimpleHealthCheckController.php index 8589c750..0e3606c1 100644 --- a/src/Http/Controllers/SimpleHealthCheckController.php +++ b/src/Http/Controllers/SimpleHealthCheckController.php @@ -5,6 +5,8 @@ use Illuminate\Http\Request; use Illuminate\Http\Response; use Illuminate\Support\Facades\Artisan; +use Illuminate\Support\Facades\Cache; +use Spatie\Health\Commands\PauseHealthChecksCommand; use Spatie\Health\Commands\RunHealthChecksCommand; use Spatie\Health\ResultStores\ResultStore; use Symfony\Component\HttpKernel\Exception\ServiceUnavailableHttpException; @@ -13,7 +15,10 @@ class SimpleHealthCheckController { public function __invoke(Request $request, ResultStore $resultStore): Response { - if ($request->has('fresh') || config('health.oh_dear_endpoint.always_send_fresh_results')) { + if ( + ($request->has('fresh') || config('health.oh_dear_endpoint.always_send_fresh_results')) + && Cache::missing(PauseHealthChecksCommand::CACHE_KEY) + ) { Artisan::call(RunHealthChecksCommand::class); } diff --git a/tests/Commands/PauseChecksCommandTest.php b/tests/Commands/PauseChecksCommandTest.php new file mode 100644 index 00000000..567ac4ad --- /dev/null +++ b/tests/Commands/PauseChecksCommandTest.php @@ -0,0 +1,49 @@ +shouldReceive('put') + ->once() + ->with( + PauseHealthChecksCommand::CACHE_KEY, + true, + PauseHealthChecksCommand::DEFAULT_TTL + ) + ->andReturn(true); + + Cache::swap($mockRepository); + + Cache::shouldReceive('driver')->andReturn($mockRepository); + + artisan(PauseHealthChecksCommand::class) + ->assertSuccessful() + ->expectsOutputToContain('All health check paused until'); +}); + +it('sets cache value to true for custom ttl', function () { + $mockRepository = Mockery::mock(Repository::class); + + $mockRepository->shouldReceive('put') + ->once() + ->with( + PauseHealthChecksCommand::CACHE_KEY, + true, + 60 + ) + ->andReturn(true); + + Cache::swap($mockRepository); + + Cache::shouldReceive('driver')->andReturn($mockRepository); + + artisan(PauseHealthChecksCommand::class, ['seconds' => '60']) + ->assertSuccessful() + ->expectsOutputToContain('All health check paused until'); +}); diff --git a/tests/Commands/ResumeChecksCommandTest.php b/tests/Commands/ResumeChecksCommandTest.php new file mode 100644 index 00000000..5bf42ba1 --- /dev/null +++ b/tests/Commands/ResumeChecksCommandTest.php @@ -0,0 +1,26 @@ +shouldReceive('forget') + ->once() + ->with(PauseHealthChecksCommand::CACHE_KEY) + ->andReturn(true); + + Cache::swap($mockRepository); + + Cache::shouldReceive('driver')->andReturn($mockRepository); + + artisan(ResumeHealthChecksCommand::class) + ->assertSuccessful() + ->expectsOutput('All health check resumed') + ; +}); diff --git a/tests/Commands/RunChecksCommandTest.php b/tests/Commands/RunChecksCommandTest.php index da3f7ecb..e393ad7e 100644 --- a/tests/Commands/RunChecksCommandTest.php +++ b/tests/Commands/RunChecksCommandTest.php @@ -1,6 +1,8 @@ assertSuccessful(); artisan('health:check --fail-command-on-failing-check')->assertFailed(); }); + +it('does not perform checks if checks are paused', function () { + $mockRepository = Mockery::mock(Repository::class); + + $mockRepository->shouldReceive('get') + ->once() + ->with(PauseHealthChecksCommand::CACHE_KEY) + ->andReturn(true); + + Cache::swap($mockRepository); + + Cache::shouldReceive('driver')->andReturn($mockRepository); + + artisan('health:check')->assertSuccessful()->expectsOutput('Checks paused'); + + $historyItems = HealthCheckResultHistoryItem::get(); + + expect($historyItems)->toHaveCount(0); +}); diff --git a/tests/Http/Controllers/SimpleHealthCheckControllerTest.php b/tests/Http/Controllers/SimpleHealthCheckControllerTest.php index f892ca57..7343e655 100644 --- a/tests/Http/Controllers/SimpleHealthCheckControllerTest.php +++ b/tests/Http/Controllers/SimpleHealthCheckControllerTest.php @@ -1,5 +1,7 @@ shouldReceive('missing') + ->once() + ->with(PauseHealthChecksCommand::CACHE_KEY) + ->andReturn(false); + + Cache::swap($mockRepository); + + Cache::shouldReceive('driver')->andReturn($mockRepository); + + // If the RunHealthChecksCommand were called (instead of being skipped as expected), + // the test should fail with the error similar to: + // "Received Mockery_2_Illuminate_Contracts_Cache_Repository::get(), but no expectations were specified." + $json = getJson('/') + ->assertOk() + ->json(); + + assertMatchesSnapshot($json); +}); diff --git a/tests/__snapshots__/SimpleHealthCheckControllerTest__it_does_not_perform_checks_if_checks_are_paused__1.yml b/tests/__snapshots__/SimpleHealthCheckControllerTest__it_does_not_perform_checks_if_checks_are_paused__1.yml new file mode 100644 index 00000000..a870ec99 --- /dev/null +++ b/tests/__snapshots__/SimpleHealthCheckControllerTest__it_does_not_perform_checks_if_checks_are_paused__1.yml @@ -0,0 +1 @@ +healthy: true