96 lines
2.8 KiB
PHP
96 lines
2.8 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Steps\Customer;
|
|
|
|
use App\Models\Instance;
|
|
use App\Models\Order;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Models\Subscription;
|
|
use App\Provisioning\Contracts\ProvisioningStep;
|
|
use App\Provisioning\Steps\ManagesRunResources;
|
|
use App\Services\Proxmox\ProxmoxClient;
|
|
use RuntimeException;
|
|
|
|
/**
|
|
* Shared helpers for customer-provisioning steps: subject/instance/plan
|
|
* resolution, guest-agent command execution, and idempotency breadcrumbs.
|
|
*/
|
|
abstract class CustomerStep implements ProvisioningStep
|
|
{
|
|
use ManagesRunResources;
|
|
|
|
public function label(): string
|
|
{
|
|
return 'provisioning.step.'.$this->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<string, mixed>
|
|
*/
|
|
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'] ?? '');
|
|
}
|
|
}
|