key(); } public function maxDuration(): int { return 300; } protected function order(ProvisioningRun $run): Order { /** @var Order $order */ $order = $run->subject; return $order; } protected function instance(ProvisioningRun $run): ?Instance { $id = $run->context('instance_id'); return $id ? Instance::query()->find($id) : null; } /** The contract this run is carrying out, or null if none was opened. */ protected function subscription(ProvisioningRun $run): ?Subscription { return Subscription::query()->where('order_id', $this->order($run)->id)->first(); } /** * What the customer actually bought — read from their frozen snapshot, never * from config('provisioning.plans'). * * The catalogue says what we sell TODAY. Sizing a machine from it would mean * that shrinking a plan resizes the VM of a customer who bought the larger * one, on their next run. Empty when no contract exists, so ValidateOrder * can fail the run closed instead of quietly falling back to the catalogue. * * @return array */ protected function plan(ProvisioningRun $run): array { $subscription = $this->subscription($run); if ($subscription === null) { return []; } return $subscription->only([ 'plan', 'term', 'price_cents', 'currency', 'quota_gb', 'traffic_gb', 'seats', 'ram_mb', 'cores', 'disk_gb', 'performance', 'template_vmid', 'tier', ]); } /** * Run a command inside the guest via qemu-guest-agent. Throws on a non-zero * exit code so the runner turns it into a retry. */ protected function guest(ProxmoxClient $pve, ProvisioningRun $run, string $command): string { $node = (string) $run->context('node'); $vmid = (int) $run->context('vmid'); $result = $pve->guestExec($node, $vmid, $command); if ((int) ($result['exitcode'] ?? 1) !== 0) { throw new RuntimeException("guest exec failed (exit {$result['exitcode']}): {$command}"); } return (string) ($result['out-data'] ?? ''); } }