140 lines
5.2 KiB
PHP
140 lines
5.2 KiB
PHP
<?php
|
|
|
|
namespace App\Provisioning\Jobs;
|
|
|
|
use App\Models\Instance;
|
|
use App\Models\Seat;
|
|
use App\Services\Nextcloud\NextcloudUsers;
|
|
use Illuminate\Bus\Queueable;
|
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
|
use Illuminate\Foundation\Bus\Dispatchable;
|
|
use Illuminate\Queue\InteractsWithQueue;
|
|
use Illuminate\Queue\SerializesModels;
|
|
use Throwable;
|
|
|
|
/**
|
|
* Spiegelt einen Sitz in die Nextcloud des Kunden.
|
|
*
|
|
* Laeuft auf der `provisioning`-Warteschlange, weil nur dieser Arbeiter den
|
|
* Tunnel und die Proxmox-Zugangsdaten hat — das Portal hat weder das eine noch
|
|
* das andere und darf es auch gar nicht versuchen.
|
|
*
|
|
* Daraus folgt: der Knopf wirkt nicht sofort. Deshalb schreibt dieser Auftrag
|
|
* zurueck, was WIRKLICH passiert ist (`nc_state`, `nc_error`) — sonst drueckt
|
|
* der Inhaber wieder und wieder, weil nichts sichtbar geschieht. Genau die
|
|
* Beschwerde, die dieses Projekt schon zweimal hatte.
|
|
*
|
|
* `tries = 1`: ein blinder Wiederholungslauf gegen einen nicht erreichbaren
|
|
* Gast bringt nichts, und der Inhaber hat einen Knopf dafuer.
|
|
*/
|
|
class SyncSeatToNextcloud implements ShouldQueue
|
|
{
|
|
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
|
|
|
public int $tries = 1;
|
|
|
|
public function __construct(
|
|
public string $seatUuid,
|
|
/** invite | role | disable | enable */
|
|
public string $action,
|
|
) {
|
|
$this->onConnection('provisioning');
|
|
$this->onQueue('provisioning');
|
|
}
|
|
|
|
/** Auch was an handle() vorbeifliegt, muss am Sitz sichtbar werden. */
|
|
public function failed(?Throwable $e): void
|
|
{
|
|
Seat::query()->where('uuid', $this->seatUuid)->update([
|
|
'nc_state' => Seat::STATE_FAILED,
|
|
'nc_error' => 'unexpected',
|
|
]);
|
|
}
|
|
|
|
public function handle(NextcloudUsers $users): void
|
|
{
|
|
$seat = Seat::query()->with('customer')->where('uuid', $this->seatUuid)->first();
|
|
|
|
if ($seat === null) {
|
|
return;
|
|
}
|
|
|
|
// Erneut geprueft, nicht der Seite geglaubt: eine geschlossene Instanz
|
|
// kann ihre VMID auf demselben Host weiterverliehen haben — dieselbe
|
|
// Falle, die in IssueInstanceAdminAccess schon beschrieben steht.
|
|
$instance = $seat->customer?->instances()
|
|
->whereIn('status', ['active', 'cancellation_scheduled'])
|
|
->latest('id')->first();
|
|
|
|
if ($instance === null || $instance->host === null || blank($instance->vmid)) {
|
|
$this->record($seat, false, 'no_instance');
|
|
|
|
return;
|
|
}
|
|
|
|
$ok = match ($this->action) {
|
|
'invite' => $this->invite($users, $instance, $seat),
|
|
'role' => $users->applyRole($instance, $seat),
|
|
'disable' => $users->disable($instance, $seat),
|
|
'enable' => $users->enable($instance, $seat),
|
|
default => false,
|
|
};
|
|
|
|
$this->record($seat, $ok, $ok ? null : 'guest_failed');
|
|
}
|
|
|
|
/**
|
|
* Einladen setzt in einem Zug auch die Gruppe: ein Benutzer, der angelegt
|
|
* ist und in keiner Rolle steckt, sieht in seiner neuen Cloud nichts und
|
|
* meldet sich am ersten Tag beim Inhaber.
|
|
*
|
|
* Das Anlegen wird dabei SOFORT vermerkt, noch bevor die Gruppe gesetzt
|
|
* wird. `nc_synced_at` beantwortet die Frage „gibt es dieses Konto dort
|
|
* ueberhaupt?", und ab dem geglueckten `user:add` lautet die Antwort ja —
|
|
* auch wenn der naechste Befehl scheitert. Die Seite entscheidet genau
|
|
* daran, ob ein spaeteres Sperren etwas zu sperren haette; ohne diese Zeile
|
|
* bliebe der gefaehrlichste Fall offen: Konto angelegt, Rolle gescheitert,
|
|
* Zugang entzogen — und nie eine Sperre losgeschickt.
|
|
*
|
|
* `nc_state` bleibt davon unberuehrt: der Versuch ist erst gelungen, wenn
|
|
* auch die Rolle sitzt.
|
|
*/
|
|
private function invite(NextcloudUsers $users, Instance $instance, Seat $seat): bool
|
|
{
|
|
// VOR dem Vermerk gelesen: war dieser Sitz schon einmal in der
|
|
// Nextcloud, ist dies eine Wiederaufnahme — und sein Konto dort steht
|
|
// gesperrt, weil `revoke()` ein `disable` geschickt hat. `user:welcome`
|
|
// macht eine Sperre nicht rueckgaengig; ohne diese Zeile bekaeme der
|
|
// Wiederaufgenommene eine Willkommensmail zu einer Tuer, die zu bleibt.
|
|
// Bei einem frisch angelegten Konto entfaellt der Befehl ganz, statt
|
|
// sich auf ein folgenloses `user:enable` zu verlassen.
|
|
$wiederaufnahme = $seat->nc_synced_at !== null;
|
|
|
|
if (! $users->invite($instance, $seat)) {
|
|
return false;
|
|
}
|
|
|
|
$seat->forceFill(['nc_synced_at' => now()])->save();
|
|
|
|
if ($wiederaufnahme && ! $users->enable($instance, $seat)) {
|
|
return false;
|
|
}
|
|
|
|
return $users->applyRole($instance, $seat);
|
|
}
|
|
|
|
/**
|
|
* `nc_synced_at` bleibt beim Fehlschlag stehen, statt geleert zu werden:
|
|
* wann dieser Sitz zuletzt WIRKLICH stimmte, ist genau die Angabe, die
|
|
* einen Fehlschlag einordnet — heute Morgen oder vor drei Wochen.
|
|
*/
|
|
private function record(Seat $seat, bool $ok, ?string $grund): void
|
|
{
|
|
$seat->forceFill([
|
|
'nc_state' => $ok ? Seat::STATE_SYNCED : Seat::STATE_FAILED,
|
|
'nc_error' => $grund,
|
|
'nc_synced_at' => $ok ? now() : $seat->nc_synced_at,
|
|
])->save();
|
|
}
|
|
}
|