Bestehende Instanzen bekommen den Mailversand nachgetragen
Ohne diesen Lauf bliebe die Mitarbeiterverwaltung fuer jeden Altkunden tot: die Einladung geht von SEINER Nextcloud aus, und die kann bis heute nichts verschicken. Ein Befehl, kein Zeitplan — dieselbe Begruendung wie bei clupilot:refresh-host-firewall. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>feat/versandtakt
parent
b92176ee59
commit
3bd0d56077
|
|
@ -0,0 +1,96 @@
|
|||
<?php
|
||||
|
||||
namespace App\Console\Commands;
|
||||
|
||||
use App\Models\Instance;
|
||||
use App\Models\ProvisioningRun;
|
||||
use App\Provisioning\Jobs\AdvanceRunJob;
|
||||
use Illuminate\Console\Command;
|
||||
|
||||
/**
|
||||
* Traegt bestehenden Instanzen den Mailversand nach.
|
||||
*
|
||||
* Der Anlass: der Versand kam erst mit der Mitarbeiterverwaltung in die
|
||||
* Pipeline. Jede vorher gebaute Instanz kann bis heute keine Mail verschicken
|
||||
* — und die Einladung an einen Mitarbeiter geht von DORT aus, nicht von
|
||||
* CluPilot. Ohne diesen Lauf bliebe die Verwaltung fuer alle Altkunden tot.
|
||||
*
|
||||
* Ein Befehl und kein Zeitplan, aus derselben Begruendung wie bei
|
||||
* `clupilot:refresh-host-firewall`: das Loch ist endlich und schliesst sich
|
||||
* endgueltig; ein naechtlicher Lauf wuerde am Tag, an dem der Schritt in der
|
||||
* Pipeline still kaputtginge, fuer ihn einspringen und das Versagen
|
||||
* verbergen; und eine Reparatur, die der Betreiber anstoesst, ist eine, deren
|
||||
* Ausgabe er liest.
|
||||
*/
|
||||
class ConfigureInstanceMail extends Command
|
||||
{
|
||||
protected $signature = 'clupilot:configure-instance-mail
|
||||
{--dry-run : auflisten, was liefe, und nichts anfassen}
|
||||
{--instance= : nur diese eine Instanz, per uuid}';
|
||||
|
||||
protected $description = 'Traegt bestehenden Kundeninstanzen den Mailversand nach';
|
||||
|
||||
public function handle(): int
|
||||
{
|
||||
$dryRun = (bool) $this->option('dry-run');
|
||||
$gestartet = 0;
|
||||
|
||||
/** @var array<string, int> Grund => Anzahl */
|
||||
$uebersprungen = [];
|
||||
|
||||
$query = Instance::query()->where('status', 'active');
|
||||
|
||||
if ($uuid = $this->option('instance')) {
|
||||
$query->where('uuid', $uuid);
|
||||
}
|
||||
|
||||
foreach ($query->with('host')->get() as $instance) {
|
||||
if ($instance->host === null) {
|
||||
$this->warn("{$instance->uuid}: kein Host — uebersprungen");
|
||||
$uebersprungen['kein Host'] = ($uebersprungen['kein Host'] ?? 0) + 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if (blank($instance->vmid)) {
|
||||
$this->warn("{$instance->uuid}: keine VMID — uebersprungen");
|
||||
$uebersprungen['keine VMID'] = ($uebersprungen['keine VMID'] ?? 0) + 1;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($dryRun) {
|
||||
$this->line("{$instance->uuid}: wuerde nachgetragen");
|
||||
$gestartet++;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$run = ProvisioningRun::create([
|
||||
'pipeline' => 'instance-mail',
|
||||
'subject_type' => Instance::class,
|
||||
'subject_id' => $instance->id,
|
||||
'status' => ProvisioningRun::STATUS_RUNNING,
|
||||
'current_step' => 0,
|
||||
'context' => [
|
||||
'instance_id' => $instance->id,
|
||||
'node' => $instance->host->node ?? 'pve',
|
||||
'vmid' => (int) $instance->vmid,
|
||||
],
|
||||
]);
|
||||
|
||||
AdvanceRunJob::dispatch($run->id);
|
||||
$this->info("{$instance->uuid}: Lauf {$run->id} gestartet");
|
||||
$gestartet++;
|
||||
}
|
||||
|
||||
$this->newLine();
|
||||
$this->line($dryRun ? "{$gestartet} Instanz(en) waeren nachgetragen worden." : "{$gestartet} Lauf/Laeufe gestartet.");
|
||||
|
||||
foreach ($uebersprungen as $grund => $anzahl) {
|
||||
$this->line("uebersprungen ({$grund}): {$anzahl}");
|
||||
}
|
||||
|
||||
return self::SUCCESS;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
use App\Models\Host;
|
||||
use App\Models\Instance;
|
||||
use App\Models\ProvisioningRun;
|
||||
use Illuminate\Support\Facades\Queue;
|
||||
|
||||
// QUEUE_CONNECTION ist in dieser Suite `sync`: ohne das hier faehrt jedes
|
||||
// `AdvanceRunJob::dispatch()` den Lauf sofort im Test, gegen eine echte,
|
||||
// unerreichbare Adresse. Die Laeufe, die dieser Test wirklich fahren will,
|
||||
// stoesst er unten selbst an.
|
||||
beforeEach(fn () => Queue::fake());
|
||||
|
||||
/**
|
||||
* Eine aktive Instanz auf einem Host mit vmid.
|
||||
*
|
||||
* Der Befehl ueberspringt Instanzen ohne Host oder ohne vmid. Ein Test, der
|
||||
* die Mailversand-Nachruestung fahren will, braucht beide.
|
||||
*/
|
||||
function aktiveInstanzMitHost(array $attributes = []): Instance
|
||||
{
|
||||
$host = Host::factory()->active()->create(['datacenter' => 'fsn', 'node' => 'pve']);
|
||||
|
||||
return Instance::factory()->create(array_merge([
|
||||
'host_id' => $host->id,
|
||||
'vmid' => 101,
|
||||
'status' => 'active',
|
||||
], $attributes));
|
||||
}
|
||||
|
||||
it('startet je aktiver Instanz einen Lauf', function () {
|
||||
aktiveInstanzMitHost();
|
||||
aktiveInstanzMitHost(['vmid' => 102]);
|
||||
Instance::factory()->create(['status' => 'closed']);
|
||||
|
||||
$this->artisan('clupilot:configure-instance-mail')->assertSuccessful();
|
||||
|
||||
expect(ProvisioningRun::where('pipeline', 'instance-mail')->count())->toBe(2);
|
||||
});
|
||||
|
||||
it('faengt unter --dry-run gar nichts an', function () {
|
||||
aktiveInstanzMitHost();
|
||||
aktiveInstanzMitHost(['vmid' => 102]);
|
||||
aktiveInstanzMitHost(['vmid' => 103]);
|
||||
|
||||
$this->artisan('clupilot:configure-instance-mail', ['--dry-run' => true])->assertSuccessful();
|
||||
|
||||
expect(ProvisioningRun::where('pipeline', 'instance-mail')->count())->toBe(0);
|
||||
});
|
||||
|
||||
it('nimmt mit --instance genau eine', function () {
|
||||
$eine = aktiveInstanzMitHost();
|
||||
aktiveInstanzMitHost(['vmid' => 102]);
|
||||
|
||||
$this->artisan('clupilot:configure-instance-mail', ['--instance' => $eine->uuid])->assertSuccessful();
|
||||
|
||||
expect(ProvisioningRun::where('pipeline', 'instance-mail')->count())->toBe(1);
|
||||
});
|
||||
|
||||
it('ueberspringt eine Instanz ohne Host und sagt es', function () {
|
||||
Instance::factory()->create(['status' => 'active', 'host_id' => null, 'vmid' => 101]);
|
||||
|
||||
$this->artisan('clupilot:configure-instance-mail')
|
||||
->expectsOutputToContain('kein Host')
|
||||
->assertSuccessful();
|
||||
|
||||
expect(ProvisioningRun::where('pipeline', 'instance-mail')->count())->toBe(0);
|
||||
});
|
||||
|
||||
it('ueberspringt eine Instanz ohne vmid und sagt es', function () {
|
||||
$host = Host::factory()->active()->create(['datacenter' => 'fsn', 'node' => 'pve']);
|
||||
Instance::factory()->create(['status' => 'active', 'host_id' => $host->id, 'vmid' => null]);
|
||||
|
||||
$this->artisan('clupilot:configure-instance-mail')
|
||||
->expectsOutputToContain('keine VMID')
|
||||
->assertSuccessful();
|
||||
|
||||
expect(ProvisioningRun::where('pipeline', 'instance-mail')->count())->toBe(0);
|
||||
});
|
||||
Loading…
Reference in New Issue