122 lines
4.7 KiB
PHP
122 lines
4.7 KiB
PHP
<?php
|
|
|
|
namespace App\Actions;
|
|
|
|
use App\Models\Instance;
|
|
use App\Models\Order;
|
|
use App\Models\ProvisioningRun;
|
|
use App\Provisioning\Jobs\AdvanceRunJob;
|
|
use App\Provisioning\WorkInFlight;
|
|
use Illuminate\Support\Facades\Cache;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
/**
|
|
* Make an instance's address true again — the router, the certificate and the
|
|
* hostname Nextcloud answers to — without touching anything else about it.
|
|
*
|
|
* A custom domain used to be announced and never served: the portal, the
|
|
* credentials mail and Instance::address() all reported it the moment its TXT
|
|
* proof appeared, while the only thing that ever wrote a route was the initial
|
|
* provisioning run, and it wrote the platform subdomain. This is the missing
|
|
* half — the moment a proven domain becomes an address, and the moment a
|
|
* withdrawn one stops being one.
|
|
*
|
|
* Called from the three places the address can change: the nightly proof check
|
|
* when verification flips, the customer's own domain page, and a package change
|
|
* that takes the right to a domain away. Deliberately not called from anywhere
|
|
* that merely LOOKS at the domain — a re-apply is remote work on a live
|
|
* machine, so it happens on a change and not on a schedule.
|
|
*/
|
|
class ReapplyInstanceAddress
|
|
{
|
|
/**
|
|
* Start an address run for this instance, or return null when there is
|
|
* nothing to start.
|
|
*
|
|
* Null is the ordinary answer, not a failure: an instance still being
|
|
* built, one whose machine no longer exists, or one that already has a run
|
|
* in flight all have their address applied by that run instead.
|
|
*/
|
|
public function __invoke(?Instance $instance): ?ProvisioningRun
|
|
{
|
|
if ($instance === null || ! $this->isReapplyable($instance)) {
|
|
return null;
|
|
}
|
|
|
|
// The lock closes the window between asking whether a run exists and
|
|
// creating one. Two requests can reach this in the same instant — the
|
|
// customer's own "check now" and the nightly command are the obvious
|
|
// pair — and two runs against one machine would write the same router
|
|
// twice and race each other's occ calls. Nobody waits for the lock: a
|
|
// re-apply that lost the race has nothing to add, because the run that
|
|
// won reads the same domain state it would have.
|
|
$lock = Cache::lock('instance-address:'.$instance->uuid, 30);
|
|
|
|
if (! $lock->get()) {
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
return $this->start($instance);
|
|
} finally {
|
|
$lock->release();
|
|
}
|
|
}
|
|
|
|
private function start(Instance $instance): ?ProvisioningRun
|
|
{
|
|
$order = $instance->order;
|
|
|
|
// A run already under way applies whatever the domain state says when it
|
|
// gets to the step — but only if it HAS the step. Asked of the two steps
|
|
// this pipeline consists of rather than of the mere existence of a run:
|
|
// `customer` and `plan-change` both carry them and there is nothing to
|
|
// add, while `restart`, `storage` and `quota` carry neither, and standing
|
|
// aside for one of those was how a domain proven during a restart stayed
|
|
// unrouted for good. See App\Provisioning\WorkInFlight.
|
|
if (app(WorkInFlight::class)->covers($order, 'address')) {
|
|
return null;
|
|
}
|
|
|
|
$run = ProvisioningRun::create([
|
|
'subject_type' => Order::class,
|
|
'subject_id' => $order->id,
|
|
'pipeline' => 'address',
|
|
'status' => ProvisioningRun::STATUS_PENDING,
|
|
'current_step' => 0,
|
|
// Everything the two steps read. instance_id is how CustomerStep
|
|
// finds the machine; node and vmid are how it reaches inside it.
|
|
'context' => [
|
|
'instance_id' => $instance->id,
|
|
'host_id' => $instance->host_id,
|
|
'node' => $instance->host?->node,
|
|
'vmid' => $instance->vmid,
|
|
'subdomain' => $instance->subdomain,
|
|
],
|
|
]);
|
|
|
|
AdvanceRunJob::dispatch($run->uuid);
|
|
|
|
Log::info('Re-applying an instance address.', [
|
|
'instance' => $instance->uuid,
|
|
'domain' => $instance->custom_domain,
|
|
'verified' => $instance->domainIsVerified(),
|
|
'run' => $run->uuid,
|
|
]);
|
|
|
|
return $run;
|
|
}
|
|
|
|
/**
|
|
* Is there a machine here whose address can be re-applied at all?
|
|
*
|
|
* The question is not this action's alone — a plan change asks exactly the
|
|
* same one before it starts a run against a live machine — so the rule sits
|
|
* on the model and both read it there. See Instance::hasLiveMachine().
|
|
*/
|
|
private function isReapplyable(Instance $instance): bool
|
|
{
|
|
return $instance->hasLiveMachine();
|
|
}
|
|
}
|