feat(ssh): add FleetService::testConnection credential probe
Connect + trivial exec against a server's stored credential; never throws,
returns {ok,error}. Used by the create-server guard to verify the SSH login
before persisting.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
feat/v1-foundation
parent
ed909edf3e
commit
2c25303903
|
|
@ -118,6 +118,29 @@ class FleetService
|
|||
return array_map(fn (array $l): string => implode("\n", $l), $parts);
|
||||
}
|
||||
|
||||
/**
|
||||
* Probe the server's stored credential: connect + a trivial exec. NEVER throws —
|
||||
* returns the failure reason instead, for the create-server guard to surface.
|
||||
* Uses a short timeout so an unreachable host fails fast.
|
||||
*
|
||||
* @return array{ok: bool, error: ?string}
|
||||
*/
|
||||
public function testConnection(Server $server): array
|
||||
{
|
||||
try {
|
||||
$ssh = (new SshClient($this->vault, timeout: 10))->connect($server);
|
||||
try {
|
||||
[, $code] = $ssh->run('true');
|
||||
} finally {
|
||||
$ssh->disconnect();
|
||||
}
|
||||
|
||||
return ['ok' => $code === 0, 'error' => null];
|
||||
} catch (Throwable $e) {
|
||||
return ['ok' => false, 'error' => $e->getMessage()];
|
||||
}
|
||||
}
|
||||
|
||||
// ── Metrics (light — used by the poller and dashboard fallback) ──────
|
||||
|
||||
/** @return array{cpu:int,mem:int,disk:int,load:float} */
|
||||
|
|
|
|||
|
|
@ -0,0 +1,27 @@
|
|||
<?php
|
||||
|
||||
namespace Tests\Feature;
|
||||
|
||||
use App\Models\Server;
|
||||
use App\Services\FleetService;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class FleetTestConnectionTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function test_unreachable_host_returns_error_without_throwing(): void
|
||||
{
|
||||
$server = Server::create(['name' => 'x', 'ip' => '127.0.0.1', 'ssh_port' => 1, 'status' => 'pending']);
|
||||
$server->credential()->create([
|
||||
'username' => 'root', 'auth_type' => 'password', 'secret' => 'nope',
|
||||
]);
|
||||
$server->load('credential');
|
||||
|
||||
$res = app(FleetService::class)->testConnection($server);
|
||||
|
||||
$this->assertFalse($res['ok']);
|
||||
$this->assertNotNull($res['error']);
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue