host($run); $required = $this->requiredTemplateVmids(); // A catalogue with nothing published yet requires no template. This is a // real state — an installation being set up for the first time onboards // its host before it publishes its plans — and failing here would be // inventing a requirement nobody has stated. if ($required === []) { return StepResult::advance(); } $client = $this->pve->forHost($host); try { $nodes = $client->listNodes(); $node = $nodes[0]['node'] ?? 'pve'; $missing = array_values(array_filter( $required, fn (int $vmid) => ! $client->vmExists($node, $vmid), )); } catch (Throwable $e) { // The API was answering one step ago; a hiccup here is transient. return StepResult::retry(15, 'could not ask Proxmox about the VM templates: '.$e->getMessage()); } if ($missing !== []) { return StepResult::fail( 'This host has no VM template to clone from, so it could not serve a single order. Missing on node '. $node.': VMID '.implode(', ', $missing).', which '. (count($missing) === 1 ? 'a published plan version points' : 'published plan versions point').' at. '. 'Copy the golden template onto this host (or restore it from another node) and retry — otherwise the '. 'first paid order placed here fails in clone_vm, after the customer has paid.' ); } return StepResult::advance(); } /** * Every template a currently sellable plan version points at. * * Published versions whose window has not closed, rather than * PlanVersion::available() — a version scheduled to launch next week is not * "available" yet and its template gap would then surface at its first order * instead of here, which is the whole failure this step removes. A closed * window is different: nobody can buy it any more, so its template is not * this host's problem. * * @return array */ private function requiredTemplateVmids(): array { return PlanVersion::query() ->whereNotNull('published_at') ->whereNotNull('template_vmid') ->where(fn ($q) => $q->whereNull('available_until')->orWhere('available_until', '>', now())) ->pluck('template_vmid') ->map(fn ($vmid) => (int) $vmid) ->unique() ->sort() ->values() ->all(); } }