From 3bd0d5607780683fbd7d3491b971d944ec849601 Mon Sep 17 00:00:00 2001 From: nexxo Date: Mon, 3 Aug 2026 20:25:08 +0200 Subject: [PATCH] Bestehende Instanzen bekommen den Mailversand nachgetragen MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../Commands/ConfigureInstanceMail.php | 96 +++++++++++++++++++ .../ConfigureInstanceMailCommandTest.php | 79 +++++++++++++++ 2 files changed, 175 insertions(+) create mode 100644 app/Console/Commands/ConfigureInstanceMail.php create mode 100644 tests/Feature/Console/ConfigureInstanceMailCommandTest.php diff --git a/app/Console/Commands/ConfigureInstanceMail.php b/app/Console/Commands/ConfigureInstanceMail.php new file mode 100644 index 0000000..41d29d9 --- /dev/null +++ b/app/Console/Commands/ConfigureInstanceMail.php @@ -0,0 +1,96 @@ +option('dry-run'); + $gestartet = 0; + + /** @var array 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; + } +} diff --git a/tests/Feature/Console/ConfigureInstanceMailCommandTest.php b/tests/Feature/Console/ConfigureInstanceMailCommandTest.php new file mode 100644 index 0000000..37ca0f3 --- /dev/null +++ b/tests/Feature/Console/ConfigureInstanceMailCommandTest.php @@ -0,0 +1,79 @@ + 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); +});