Skip to content

Commit

Permalink
Merge branch 'spatie:main' into add-summaray-for-detailed-check
Browse files Browse the repository at this point in the history
  • Loading branch information
pelmered authored Jul 26, 2024
2 parents 023cbe3 + 696dad4 commit 875303e
Show file tree
Hide file tree
Showing 10 changed files with 177 additions and 9 deletions.
13 changes: 13 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,19 @@

All notable changes to `laravel-health` will be documented in this file.

## 1.29.4 - 2024-07-22

### What's Changed

* Allow status code of Json results controller to be changed on failure by @rbibby in https://github.com/spatie/laravel-health/pull/232
* Add support for checking backups on filesystem disks by @pelmered in https://github.com/spatie/laravel-health/pull/237

### New Contributors

* @pelmered made their first contribution in https://github.com/spatie/laravel-health/pull/237

**Full Changelog**: https://github.com/spatie/laravel-health/compare/1.29.3...1.29.4

## 1.29.3 - 2024-07-15

### What's Changed
Expand Down
6 changes: 6 additions & 0 deletions config/health.php
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,10 @@
* in Horizon's silenced jobs screen.
*/
'silence_health_queue_job' => true,

/*
* The response code to use for HealthCheckJsonResultsController when a health
* check has failed
*/
'json_results_failure_status' => 200,
];
5 changes: 5 additions & 0 deletions docs/viewing-results/as-json.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,8 @@ https://example.com/health?fresh
```

This way you'll see the latest results in the JSON.

## Status codes

By default, a 200 response will be returned regardless of the results. You can change the status code that
will be returned if a check fails using the `health.json_results_failure_status` config value.
28 changes: 20 additions & 8 deletions src/Checks/Checks/BackupsCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
namespace Spatie\Health\Checks\Checks;

use Carbon\Carbon;
use Illuminate\Contracts\Filesystem\Filesystem;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Storage;
use Spatie\Health\Checks\Check;
use Spatie\Health\Checks\Result;
use Spatie\Health\Support\BackupFile;
use Symfony\Component\HttpFoundation\File\File as SymfonyFile;

class BackupsCheck extends Check
{
protected ?string $locatedAt = null;

protected ?Filesystem $disk = null;

protected ?Carbon $youngestShouldHaveBeenMadeBefore = null;

protected ?Carbon $oldestShouldHaveBeenMadeAfter = null;
Expand All @@ -30,6 +35,13 @@ public function locatedAt(string $globPath): self
return $this;
}

public function onDisk($disk)
{
$this->disk = Storage::disk($disk);

return $this;
}

public function youngestBackShouldHaveBeenMadeBefore(Carbon $date): self
{
$this->youngestShouldHaveBeenMadeBefore = $date;
Expand Down Expand Up @@ -61,18 +73,18 @@ public function numberOfBackups(?int $min = null, ?int $max = null): self

public function run(): Result
{
$files = collect(File::glob($this->locatedAt));
$files = collect($this->disk ? $files = $this->disk->files($this->locatedAt) : File::glob($this->locatedAt));

if ($files->isEmpty()) {
return Result::make()->failed('No backups found');
}

$eligableBackups = $files
->map(function (string $path) {
return new SymfonyFile($path);
return new BackupFile($path, $this->disk);
})
->filter(function (SymfonyFile $file) {
return $file->getSize() >= $this->minimumSizeInMegabytes * 1024 * 1024;
->filter(function (BackupFile $file) {
return $file->size() >= $this->minimumSizeInMegabytes * 1024 * 1024;
});

if ($eligableBackups->isEmpty()) {
Expand Down Expand Up @@ -115,12 +127,12 @@ protected function youngestBackupIsToolOld(Collection $backups): bool
{
/** @var SymfonyFile|null $youngestBackup */
$youngestBackup = $backups
->sortByDesc(fn (SymfonyFile $file) => $file->getMTime())
->sortByDesc(fn (BackupFile $file) => $file->lastModified())
->first();

$threshold = $this->youngestShouldHaveBeenMadeBefore->getTimestamp();

return $youngestBackup->getMTime() <= $threshold;
return $youngestBackup->lastModified() <= $threshold;
}

/**
Expand All @@ -130,11 +142,11 @@ protected function oldestBackupIsTooYoung(Collection $backups): bool
{
/** @var SymfonyFile|null $oldestBackup */
$oldestBackup = $backups
->sortBy(fn (SymfonyFile $file) => $file->getMTime())
->sortBy(fn (BackupFile $file) => $file->lastModified())
->first();

$threshold = $this->oldestShouldHaveBeenMadeAfter->getTimestamp();

return $oldestBackup->getMTime() >= $threshold;
return $oldestBackup->lastModified() >= $threshold;
}
}
2 changes: 1 addition & 1 deletion src/Http/Controllers/HealthCheckJsonResultsController.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ public function __invoke(Request $request, ResultStore $resultStore): Response

$checkResults = $resultStore->latestResults();

return response($checkResults?->toJson() ?? '')
return response($checkResults?->toJson() ?? '', config('health.json_results_failure_status', 200))
->header('Content-Type', 'application/json')
->header('Cache-Control', 'no-store, no-cache, must-revalidate, post-check=0, pre-check=0');
}
Expand Down
35 changes: 35 additions & 0 deletions src/Support/BackupFile.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
<?php

namespace Spatie\Health\Support;

use Illuminate\Contracts\Filesystem\Filesystem;
use Symfony\Component\HttpFoundation\File\File as SymfonyFile;

class BackupFile
{
protected ?SymfonyFile $file = null;

public function __construct(
protected string $path,
protected ?Filesystem $disk = null,
) {
if (! $disk) {
$this->file = new SymfonyFile($path);
}
}

public function path(): string
{
return $this->path;
}

public function size(): int
{
return $this->file ? $this->file->getSize() : $this->disk->size($this->path);
}

public function lastModified(): int
{
return $this->file ? $this->file->getMTime() : $this->disk->lastModified($this->path);
}
}
71 changes: 71 additions & 0 deletions tests/Checks/BackupsCheckTest.php
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
<?php

use Illuminate\Support\Facades\Storage;
use Spatie\Health\Checks\Checks\BackupsCheck;
use Spatie\Health\Enums\Status;
use Spatie\Health\Facades\Health;
Expand Down Expand Up @@ -150,3 +151,73 @@
->run();
expect($result)->status->toBe(Status::failed());
});

it('will pass if the backup is at least than the given size when loaded from filesystem disk', function (int $sizeInMb) {

Storage::fake('backups');

$tempFile = $this->temporaryDirectory->path('hey.zip');

shell_exec("truncate -s {$sizeInMb}M {$tempFile}");

Storage::disk('backups')->put('backups/hey.zip', file_get_contents($tempFile));

$result = $this->backupsCheck
->onDisk('backups')
->locatedAt('backups')
->atLeastSizeInMb(5)
->run();

expect($result)->status->toBe(Status::ok());
})->with([
[5],
[6],
]);

it('can check if the youngest backup is recent enough when loaded from filesystem disk', function () {

Storage::fake('backups');
Storage::disk('backups')->put('backups/hey.zip', 'content');

testTime()->addMinutes(4);

$result = $this->backupsCheck
->onDisk('backups')
->locatedAt('backups')
->youngestBackShouldHaveBeenMadeBefore(now()->subMinutes(5)->startOfMinute())
->run();

expect($result)->status->toBe(Status::ok());

testTime()->addMinutes(2);

$result = $this->backupsCheck
->locatedAt($this->temporaryDirectory->path('*.zip'))
->youngestBackShouldHaveBeenMadeBefore(now()->subMinutes(5))
->run();
expect($result)->status->toBe(Status::failed());
});

it('can check if the oldest backup is old enough when loaded from filesystem disk', function () {

Storage::fake('backups');
Storage::disk('backups')->put('backups/hey.zip', 'content');

testTime()->addMinutes(4);

$result = $this->backupsCheck
->onDisk('backups')
->locatedAt('backups')
->oldestBackShouldHaveBeenMadeAfter(now()->subMinutes(5))
->run();

expect($result)->status->toBe(Status::failed());

testTime()->addMinutes(2);

$result = $this->backupsCheck
->locatedAt($this->temporaryDirectory->path('*.zip'))
->oldestBackShouldHaveBeenMadeAfter(now()->subMinutes(5))
->run();
expect($result)->status->toBe(Status::failed());
});
15 changes: 15 additions & 0 deletions tests/Http/Controllers/HealthCheckJsonResultsControllerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
use Spatie\Health\Http\Controllers\HealthCheckJsonResultsController;
use Spatie\Health\ResultStores\StoredCheckResults\StoredCheckResults;
use Spatie\Health\Tests\TestClasses\FakeUsedDiskSpaceCheck;
use Symfony\Component\HttpFoundation\Response;

use function Pest\Laravel\artisan;
use function Pest\Laravel\getJson;
Expand Down Expand Up @@ -56,3 +57,17 @@
expect($storedCheckResults)->toBeInstanceOf(StoredCheckResults::class)
->and($storedCheckResults->storedCheckResults)->toHaveCount(1);
});

it('will return the configured status for a unhealthy check', function () {
$this->check->replyWith(fn () => false);

config()->set('health.json_results_failure_status', Response::HTTP_SERVICE_UNAVAILABLE);

artisan(RunHealthChecksCommand::class);

$json = getJson('/')
->assertStatus(Response::HTTP_SERVICE_UNAVAILABLE)
->json();

assertMatchesSnapshot($json);
});
8 changes: 8 additions & 0 deletions tests/TestClasses/FakeUsedDiskSpaceCheck.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

namespace Spatie\Health\Tests\TestClasses;

use Closure;
use Spatie\Health\Checks\Checks\UsedDiskSpaceCheck;

class FakeUsedDiskSpaceCheck extends UsedDiskSpaceCheck
Expand All @@ -24,4 +25,11 @@ public function getFilesystemName(): ?string
{
return $this->filesystemName;
}

public function replyWith(Closure $closure): self
{
$this->closure = $closure;

return $this;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
finishedAt: 1609459200
checkResults:
- { name: FakeUsedDiskSpace, label: 'Fake Used Disk Space', notificationMessage: 'The disk is almost full (100% used).', shortSummary: 100%, status: failed, meta: { disk_space_used_percentage: 100 } }

0 comments on commit 875303e

Please sign in to comment.