diff --git a/app/Jobs/UpdateSite.php b/app/Jobs/UpdateSite.php index a278ae8..bff698b 100644 --- a/app/Jobs/UpdateSite.php +++ b/app/Jobs/UpdateSite.php @@ -6,7 +6,6 @@ use App\Exceptions\UpdateException; use App\Models\Site; -use App\Models\Update; use App\RemoteSite\Connection; use App\RemoteSite\Responses\PrepareUpdate; use Illuminate\Contracts\Queue\ShouldQueue; @@ -31,6 +30,14 @@ public function __construct(protected readonly Site $site, protected string $tar */ public function handle(): void { + $updateCount = $this->site->getUpdateCount($this->targetVersion); + + if ($updateCount >= config('autoupdates.max_update_tries')) { + Log::info("Update Loop detected for Site: " . $this->site->id . '; TargetVersion: ' . $this->targetVersion); + + return; + } + /** @var Connection $connection */ $connection = $this->site->connection; diff --git a/app/Models/Site.php b/app/Models/Site.php index 9df3285..66a4fcf 100644 --- a/app/Models/Site.php +++ b/app/Models/Site.php @@ -60,6 +60,11 @@ public function getFrontendStatus(): int return $httpClient->get($this->url)->getStatusCode(); } + public function getUpdateCount(string $targetVersion): int + { + return $this->updates()->where('new_version', $targetVersion)->count(); + } + /** * @return HasMany */ diff --git a/config/autoupdates.php b/config/autoupdates.php index 9f60fd5..0f215b9 100644 --- a/config/autoupdates.php +++ b/config/autoupdates.php @@ -4,4 +4,5 @@ 'healthcheck_interval' => env('HEALTH_CHECK_INTERVAL', 24), 'cleanup_site_delay' => env('CLEANUP_SITE_DELAY', 7), 'tuf_repo_cachetime' => env('TUF_REPO_CACHETIME', 5), + 'max_update_tries' => env('MAX_UPDATE_TRIES', 5), ]; diff --git a/tests/Unit/Jobs/UpdateSiteTest.php b/tests/Unit/Jobs/UpdateSiteTest.php index 3e6f283..725a959 100644 --- a/tests/Unit/Jobs/UpdateSiteTest.php +++ b/tests/Unit/Jobs/UpdateSiteTest.php @@ -58,6 +58,24 @@ public function testJobQuitsIfNoUpdateIsAvailable() $this->assertTrue(true); } + public function testJobQuitsIfWeDetectALoopForAVersion() + { + $site = $this->getSiteMock([], null, 6); + + Log::spy(); + + $object = new UpdateSite($site, "1.0.1"); + $object->handle(); + + Log::shouldHaveReceived('info') + ->once() + ->withArgs(function ($message) { + return str_contains($message, 'Update Loop detected for Site'); + }); + + $this->assertTrue(true); + } + public function testJobQuitsIfAvailableUpdateWouldBeAMajorUpdate() { $site = $this->getSiteMock( @@ -80,7 +98,6 @@ public function testJobQuitsIfAvailableUpdateWouldBeAMajorUpdate() $this->assertTrue(true); } - public function testJobQuitsIfAvailableUpdateDoesNotMatchTargetVersion() { $site = $this->getSiteMock( @@ -167,7 +184,7 @@ public function testJobWritesSuccessLogForSuccessfulJobs() $object->handle(); } - protected function getSiteMock(array $responses, array $expectedLogRow = null) + protected function getSiteMock(array $responses, array $expectedLogRow = null, int $updateCount = 0) { $connectionMock = $this->getMockBuilder(Connection::class) ->disableOriginalConstructor() @@ -200,12 +217,13 @@ function ($method) use ($responses) { } $siteMock = $this->getMockBuilder(Site::class) - ->onlyMethods(['getConnectionAttribute', 'getFrontendStatus', 'updates']) + ->onlyMethods(['getConnectionAttribute', 'getFrontendStatus', 'getUpdateCount', 'updates']) ->getMock(); $siteMock->method('updates')->willReturn($updateMock); $siteMock->method('getConnectionAttribute')->willReturn($connectionMock); $siteMock->method('getFrontendStatus')->willReturn(200); + $siteMock->method('getUpdateCount')->willReturn($updateCount); $siteMock->id = 1; $siteMock->url = "http://example.org"; $siteMock->cms_version = "1.0.0";