onQueue('high'); $this->delay(now()->addSeconds(self::CONFIRMATION_DELAY_SECONDS)); } /** * Call before Coolify stops or recreates the containers of a resource, so that the stop sends no notification. */ public static function expectStop(BaseModel $resource, int $stopTimeoutSeconds = 30): void { Cache::put(self::expectedStopKey($resource), true, now()->addSeconds($stopTimeoutSeconds + self::EXPECTED_STOP_SECONDS)); } /** * Read which resources are up, before a status check changes their status. * * @param iterable $resources * @return array, list> */ public static function upResourceIds(iterable $resources): array { $ids = []; foreach ($resources as $resource) { if (filled($resource->status) && ! self::isExited($resource->status)) { $ids[$resource::class][] = $resource->getKey(); } } return $ids; } /** * After a status check: queue a notification for each resource that was up before the check and is exited now. * * @param array, list> $upResourceIds from upResourceIds() */ public static function dispatchForStopped(array $upResourceIds): void { foreach ($upResourceIds as $class => $ids) { $class::query() ->whereKey($ids) ->where('status', 'like', 'exited%') ->get() ->reject(fn (BaseModel $resource): bool => self::stopIsExpected($resource)) ->each(fn (BaseModel $resource) => self::dispatch($resource)); } } public function handle(): void { $resource = $this->resource::query()->whereKey($this->resource->getKey())->first(); if ($resource === null || ! self::isExited($resource->status) || (bool) $resource->restart_limit_reached || self::stopIsExpected($resource) || self::isDeploying($resource)) { return; } $team = $resource instanceof ApplicationPreview ? $resource->application?->team() : $resource->team(); $team?->notify(new StatusChanged($resource)); } private static function isExited(?string $status): bool { return str($status)->startsWith('exited'); } private static function stopIsExpected(BaseModel $resource): bool { return Cache::has(self::expectedStopKey($resource)); } private static function expectedStopKey(BaseModel $resource): string { return 'resource-stop-expected:'.$resource->getMorphClass().':'.$resource->getKey(); } private static function isDeploying(BaseModel $resource): bool { [$applicationId, $pullRequestId] = match (true) { $resource instanceof Application => [$resource->id, 0], $resource instanceof ApplicationPreview => [$resource->application_id, $resource->pull_request_id], default => [null, null], }; if ($applicationId === null) { return false; } return ApplicationDeploymentQueue::query() ->where('application_id', (string) $applicationId) ->where('pull_request_id', $pullRequestId) ->whereIn('status', [ApplicationDeploymentStatus::QUEUED->value, ApplicationDeploymentStatus::IN_PROGRESS->value]) ->exists(); } }