Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add pause and resume commands for health checks #255

Merged
merged 2 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/basic-usage/pausing-and-resuming-checks.md
Original file line number Diff line number Diff line change
@@ -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
```
26 changes: 26 additions & 0 deletions src/Commands/PauseHealthChecksCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

namespace Spatie\Health\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;

class PauseHealthChecksCommand extends Command
{
public const CACHE_KEY = 'health_paused';
public const DEFAULT_TTL = 300;

protected $signature = 'health:pause {seconds=' . self::DEFAULT_TTL . '}';
protected $description = 'Pause all health checks for the giving time';

public function handle(): int
{
$seconds = (int) $this->argument('seconds');

Cache::put(self::CACHE_KEY, true, $seconds);

$this->comment('All health check paused until ' . now()->addSeconds($seconds)->toDateTimeString());

return self::SUCCESS;
}
}
21 changes: 21 additions & 0 deletions src/Commands/ResumeHealthChecksCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

namespace Spatie\Health\Commands;

use Illuminate\Console\Command;
use Illuminate\Support\Facades\Cache;

class ResumeHealthChecksCommand extends Command
{
protected $signature = 'health:resume';
protected $description = 'Resume all health checks';

public function handle(): int
{
Cache::forget(PauseHealthChecksCommand::CACHE_KEY);

$this->comment('All health check resumed');

return self::SUCCESS;
}
}
7 changes: 7 additions & 0 deletions src/Commands/RunHealthChecksCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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();
Expand Down
6 changes: 5 additions & 1 deletion src/HealthServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -33,7 +35,9 @@ public function configurePackage(Package $package): void
ListHealthChecksCommand::class,
RunHealthChecksCommand::class,
ScheduleCheckHeartbeatCommand::class,
DispatchQueueCheckJobsCommand::class
DispatchQueueCheckJobsCommand::class,
PauseHealthChecksCommand::class,
ResumeHealthChecksCommand::class,
);
}

Expand Down
7 changes: 6 additions & 1 deletion src/Http/Controllers/SimpleHealthCheckController.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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);
}

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

use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
use Spatie\Health\Commands\PauseHealthChecksCommand;

use function Pest\Laravel\artisan;

it('sets cache value to true for default ttl', function () {
$mockRepository = Mockery::mock(Repository::class);

$mockRepository->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');
});
26 changes: 26 additions & 0 deletions tests/Commands/ResumeChecksCommandTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Cache;
use Spatie\Health\Commands\PauseHealthChecksCommand;

use Spatie\Health\Commands\ResumeHealthChecksCommand;
use function Pest\Laravel\artisan;

it('forgets cache value', function () {
$mockRepository = Mockery::mock(Repository::class);

$mockRepository->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')
;
});
21 changes: 21 additions & 0 deletions tests/Commands/RunChecksCommandTest.php
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
<?php

use Illuminate\Contracts\Cache\Repository;
use Illuminate\Support\Facades\Notification;
use Spatie\Health\Commands\PauseHealthChecksCommand;
use Spatie\Health\Commands\RunHealthChecksCommand;
use Spatie\Health\Enums\Status;
use Spatie\Health\Facades\Health;
Expand Down Expand Up @@ -125,3 +127,22 @@
artisan('health:check')->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);
});
26 changes: 26 additions & 0 deletions tests/Http/Controllers/SimpleHealthCheckControllerTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
<?php

use Illuminate\Contracts\Cache\Repository;
use Spatie\Health\Commands\PauseHealthChecksCommand;
use Spatie\Health\Commands\RunHealthChecksCommand;
use Spatie\Health\Facades\Health;
use Spatie\Health\Http\Controllers\SimpleHealthCheckController;
Expand Down Expand Up @@ -44,3 +46,27 @@

assertMatchesSnapshot($json);
});

it('does not perform checks if checks are paused', function () {
artisan(RunHealthChecksCommand::class);

$mockRepository = Mockery::mock(Repository::class);

$mockRepository->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);
});
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
healthy: true
Loading