Skip to content

Commit

Permalink
Merge pull request #255 from faustoFF/add-pause-resume-commands
Browse files Browse the repository at this point in the history
Add pause and resume commands for health checks
  • Loading branch information
freekmurze authored Jan 13, 2025
2 parents 2bb96fd + af98da6 commit be253b0
Show file tree
Hide file tree
Showing 11 changed files with 217 additions and 2 deletions.
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

0 comments on commit be253b0

Please sign in to comment.