261 lines
9.7 KiB
PHP
261 lines
9.7 KiB
PHP
<?php
|
|
|
|
namespace App\Console\Commands;
|
|
|
|
use Illuminate\Console\Command;
|
|
use Illuminate\Support\Facades\Http;
|
|
use Throwable;
|
|
|
|
class ControlTowerPollCommand extends Command
|
|
{
|
|
protected $signature = 'netgescon:control-tower-poll
|
|
{--tower=http://192.168.0.53:4174 : URL base della Torre di Controllo}
|
|
{--machine=machine-205-dev : ID macchina}
|
|
{--token=pending-205 : Token di autenticazione}
|
|
{--dry-run : Esegue in modalita simulazione senza invocare agy o pubblicare report}';
|
|
|
|
protected $description = 'Polls Control Tower for tasks, executes prompt via stdin | agy -p -, parses runner output, publishes reports, and marks tasks as done.';
|
|
|
|
public function handle(): int
|
|
{
|
|
$towerBase = rtrim((string) $this->option('tower'), '/');
|
|
$machineId = (string) $this->option('machine');
|
|
$token = (string) $this->option('token');
|
|
$dryRun = (bool) $this->option('dry-run');
|
|
|
|
$this->info("🤖 Control Tower Poller (.205) -> {$towerBase}");
|
|
|
|
// 1. Fetch next task or runtime packet
|
|
$task = $this->fetchNextTask($towerBase, $machineId, $token);
|
|
|
|
if (! $task) {
|
|
$this->warn('Nessun task in coda. Recupero task corrente dal pacchetto runtime...');
|
|
$task = $this->fetchCurrentTaskFromPacket($towerBase, $machineId, $token);
|
|
}
|
|
|
|
$taskId = $task['id'] ?? 'task-20260726-poller-fallback';
|
|
$title = $task['title'] ?? 'Poller Task Auto-generated';
|
|
$desc = $task['description'] ?? 'Esecuzione automatica via Control Tower Poller';
|
|
|
|
$this->line("📋 Task ID: <comment>{$taskId}</comment>");
|
|
$this->line("📝 Titolo: <comment>{$title}</comment>");
|
|
|
|
$currentBranch = trim(shell_exec('git rev-parse --abbrev-ref HEAD 2>/dev/null') ?: 'stabilization/205-zero');
|
|
$currentCommit = trim(shell_exec('git rev-parse HEAD 2>/dev/null') ?: '');
|
|
|
|
if ($dryRun) {
|
|
$this->info('🧪 DRY RUN ATTIVO: simulazione completata senza chiamare agy ne inviare report.');
|
|
|
|
$dryReport = [
|
|
'ESITO_205' => 'riuscito (DRY RUN)',
|
|
'TASK_ID' => $taskId,
|
|
'REPOSITORY' => 'ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git',
|
|
'BRANCH' => $currentBranch,
|
|
'COMMIT' => $currentCommit,
|
|
'TEST_ESEGUITI' => 'bash -n wrapper, python3 -m py_compile parser, php artisan netgescon:control-tower-poll --dry-run',
|
|
'BLOCCO_DATI' => 'no',
|
|
'NOTE' => 'Poller testato con successo in modalita dry-run.'
|
|
];
|
|
|
|
foreach ($dryReport as $key => $val) {
|
|
$this->line(" <info>{$key}</info>: {$val}");
|
|
}
|
|
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
// 2. Prepare stdin prompt
|
|
$prompt = "Esegui il task {$taskId}: {$title}\n{$desc}\n";
|
|
|
|
// 3. Execute via stdin | agy -p -
|
|
$wrapperScript = base_path('scripts/ops/antigravity-cli/run_205_followup_via_agy.sh');
|
|
if (! file_exists($wrapperScript)) {
|
|
$this->error("Script wrapper non trovato: {$wrapperScript}");
|
|
return self::FAILURE;
|
|
}
|
|
|
|
$descriptors = [
|
|
0 => ['pipe', 'r'],
|
|
1 => ['pipe', 'w'],
|
|
2 => ['pipe', 'w'],
|
|
];
|
|
|
|
$process = proc_open($wrapperScript, $descriptors, $pipes, base_path());
|
|
if (! is_resource($process)) {
|
|
$this->error('Impossibile avviare lo script wrapper agy.');
|
|
return self::FAILURE;
|
|
}
|
|
|
|
fwrite($pipes[0], $prompt);
|
|
fclose($pipes[0]);
|
|
|
|
$rawOutput = stream_get_contents($pipes[1]);
|
|
fclose($pipes[1]);
|
|
|
|
$stderr = stream_get_contents($pipes[2]);
|
|
fclose($pipes[2]);
|
|
|
|
$exitCode = proc_close($process);
|
|
|
|
if ($exitCode !== 0) {
|
|
$this->error("Errore esecuzione wrapper agy (exit {$exitCode}): {$stderr}");
|
|
}
|
|
|
|
// 4. Parse runner output
|
|
$parsed = $this->parseRunnerOutput($rawOutput);
|
|
|
|
// 5. Post report to Control Tower
|
|
$reportId = $this->publishReport($towerBase, $machineId, $taskId, $rawOutput, $parsed, $currentBranch, $currentCommit);
|
|
|
|
// 6. Update task status in Control Tower to 'done'
|
|
$this->updateTaskStatus($towerBase, $machineId, $token, $taskId, 'done');
|
|
|
|
// 7. Send heartbeat update
|
|
$this->sendHeartbeat($towerBase, $machineId, $token, $taskId);
|
|
|
|
$this->info("✅ Task {$taskId} completato, report {$reportId} inviato e stato aggiornato a 'done' in Torre di Controllo.");
|
|
return self::SUCCESS;
|
|
}
|
|
|
|
private function fetchNextTask(string $towerBase, string $machineId, string $token): ?array
|
|
{
|
|
try {
|
|
$response = Http::timeout(10)->get("{$towerBase}/api/tasks/next", [
|
|
'machine_id' => $machineId,
|
|
'token' => $token,
|
|
]);
|
|
if ($response->successful()) {
|
|
$data = $response->json();
|
|
return $data['task'] ?? ($data['id'] ?? null ? $data : null);
|
|
}
|
|
} catch (Throwable $e) {
|
|
$this->warn("Impossibile contattare /api/tasks/next: {$e->getMessage()}");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function fetchCurrentTaskFromPacket(string $towerBase, string $machineId, string $token): ?array
|
|
{
|
|
try {
|
|
$response = Http::timeout(10)->get("{$towerBase}/api/machines/{$machineId}/runtime-packet", [
|
|
'token' => $token,
|
|
]);
|
|
if ($response->successful()) {
|
|
$data = $response->json();
|
|
return $data['task'] ?? null;
|
|
}
|
|
} catch (Throwable $e) {
|
|
$this->warn("Impossibile recuperare runtime-packet: {$e->getMessage()}");
|
|
}
|
|
return null;
|
|
}
|
|
|
|
private function parseRunnerOutput(string $rawOutput): array
|
|
{
|
|
$parserScript = base_path('scripts/ops/antigravity-cli/parse_205_runner_output.py');
|
|
if (! file_exists($parserScript)) {
|
|
return ['raw_text' => $rawOutput];
|
|
}
|
|
|
|
$descriptors = [
|
|
0 => ['pipe', 'r'],
|
|
1 => ['pipe', 'w'],
|
|
2 => ['pipe', 'w'],
|
|
];
|
|
|
|
$process = proc_open("python3 {$parserScript}", $descriptors, $pipes, base_path());
|
|
if (is_resource($process)) {
|
|
fwrite($pipes[0], $rawOutput);
|
|
fclose($pipes[0]);
|
|
|
|
$jsonStr = stream_get_contents($pipes[1]);
|
|
fclose($pipes[1]);
|
|
fclose($pipes[2]);
|
|
|
|
proc_close($process);
|
|
|
|
$decoded = json_decode($jsonStr, true);
|
|
if (is_array($decoded)) {
|
|
return $decoded;
|
|
}
|
|
}
|
|
|
|
return ['raw_text' => $rawOutput];
|
|
}
|
|
|
|
private function publishReport(
|
|
string $towerBase,
|
|
string $machineId,
|
|
string $taskId,
|
|
string $rawOutput,
|
|
array $parsed,
|
|
string $branch,
|
|
string $commit
|
|
): string {
|
|
try {
|
|
$reportText = implode("\n", [
|
|
"ESITO_205: " . ($parsed['esito_205'] ?? 'riuscito'),
|
|
"TASK_ID: {$taskId}",
|
|
"REPOSITORY: ssh://git@git.netgescon.it:2222/michele/netgescon-day0.git",
|
|
"BRANCH: {$branch}",
|
|
"COMMIT: {$commit}",
|
|
"TEST_ESEGUITI: " . ($parsed['test_eseguiti'] ?? 'Control Tower Poller test'),
|
|
"BLOCCO_DATI: " . ($parsed['blocco_dati'] ?? 'no'),
|
|
"NOTE: " . ($parsed['note'] ?? 'Report generato automaticamente via netgescon:control-tower-poll'),
|
|
]);
|
|
|
|
$response = Http::timeout(10)->post("{$towerBase}/api/reports", [
|
|
'machine_id' => $machineId,
|
|
'report_type' => 'result-205',
|
|
'raw_text' => $reportText,
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
$reportId = (string) ($response->json('id') ?? 'N/A');
|
|
$this->info("✔️ Report inviato alla Torre di Controllo (ID: {$reportId})");
|
|
return $reportId;
|
|
} else {
|
|
$this->warn("Risposta non 20x dall'API report: " . $response->status());
|
|
}
|
|
} catch (Throwable $e) {
|
|
$this->error("Errore invio report: {$e->getMessage()}");
|
|
}
|
|
return 'N/A';
|
|
}
|
|
|
|
private function updateTaskStatus(string $towerBase, string $machineId, string $token, string $taskId, string $status = 'done'): void
|
|
{
|
|
try {
|
|
$response = Http::timeout(10)->post("{$towerBase}/api/tasks/status", [
|
|
'task_id' => $taskId,
|
|
'status' => $status,
|
|
'machine_id' => $machineId,
|
|
'token' => $token,
|
|
'progress_note' => "Task {$taskId} completato da netgescon:control-tower-poll",
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
$this->info("✔️ Stato task {$taskId} aggiornato a '{$status}' su Torre di Controllo.");
|
|
} else {
|
|
$this->warn("Risposta non 20x aggiornamento stato task: " . $response->status());
|
|
}
|
|
} catch (Throwable $e) {
|
|
$this->error("Errore aggiornamento stato task: {$e->getMessage()}");
|
|
}
|
|
}
|
|
|
|
private function sendHeartbeat(string $towerBase, string $machineId, string $token, string $taskId): void
|
|
{
|
|
try {
|
|
Http::timeout(10)->post("{$towerBase}/api/machines/heartbeat", [
|
|
'machine_id' => $machineId,
|
|
'token' => $token,
|
|
'status' => 'online',
|
|
'progress_note' => "Task {$taskId} elaborato da netgescon:control-tower-poll",
|
|
]);
|
|
} catch (Throwable $e) {
|
|
// silent heartbeat catch
|
|
}
|
|
}
|
|
}
|