*/ public function run(): array { $required = PlanVersion::query() ->whereNotNull('published_at') ->whereNotNull('template_vmid') ->where(fn ($q) => $q->whereNull('available_until')->orWhere('available_until', '>', now())) ->pluck('template_vmid')->map(fn ($v) => (int) $v)->unique()->values()->all(); // A catalogue with no published version requires no template. The // same case VerifyVmTemplate explicitly lets through. if ($required === []) { return ['ok' => true, 'reason' => 'nothing_published']; } $hosts = Host::query()->where('status', 'active')->whereNotNull('api_token_ref')->get(); if ($hosts->isEmpty()) { return ['ok' => false, 'reason' => 'no_usable_host']; } $missing = []; foreach ($required as $vmid) { $found = false; foreach ($hosts as $host) { try { if ($this->pve->forHost($host)->vmExists($host->node ?? 'pve', $vmid)) { $found = true; break; } } catch (Throwable) { // A host that is not answering right now is not proof of // a missing template. Keep looking at the others. continue; } } if (! $found) { $missing[] = $vmid; } } return $missing === [] ? ['ok' => true, 'reason' => 'present'] : ['ok' => false, 'reason' => 'template_missing', 'vmids' => $missing]; } }