host($run); $required = PlanVersion::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'; // isTemplate, not vmExists. `template: 1` is the property a clone // needs; the number on its own is not. A build that died halfway // leaves an ordinary VM numbered 9000 behind, and an existence // check calls that fine — which puts the gap back exactly where // this step was written to take it away from: the first paid order. $missing = array_values(array_filter( $required, fn (int $vmid) => ! $client->isTemplate($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. Absent on node '. $node.', or present but not marked as a template: 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(); } }