Major additions: - Storage abstraction layer with filesystem and in-memory implementations - Gitea API integration with MCP tools for repository management - Console dialog mode with interactive command execution - WireGuard VPN DNS fix implementation and documentation - HTTP client streaming response support - Router generic result type - Parameter type validator for framework core Framework enhancements: - Console command registry improvements - Console dialog components - Method signature analyzer updates - Route mapper refinements - MCP server and tool mapper updates - Queue job chain and dependency commands - Discovery tokenizer improvements Infrastructure: - Deployment architecture documentation - Ansible playbook updates for WireGuard client regeneration - Production environment configuration updates - Docker Compose local configuration updates - Remove obsolete docker-compose.yml (replaced by environment-specific configs) Documentation: - PERMISSIONS.md for access control guidelines - WireGuard DNS fix implementation details - Console dialog mode usage guide - Deployment architecture overview Testing: - Multi-purpose attribute tests - Gitea Actions integration tests (typed and untyped)
248 lines
8.9 KiB
PHP
248 lines
8.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Framework\Queue\Commands;
|
|
|
|
use App\Framework\Console\Attributes\ConsoleCommand;
|
|
use App\Framework\Queue\Contracts\JobChainManagerInterface;
|
|
use App\Framework\Queue\ValueObjects\JobChain;
|
|
use App\Framework\Queue\ValueObjects\ChainExecutionMode;
|
|
use App\Framework\Queue\Services\JobChainExecutionCoordinator;
|
|
use App\Framework\Queue\Services\DependencyResolutionEngine;
|
|
|
|
final readonly class JobChainCommands
|
|
{
|
|
public function __construct(
|
|
private JobChainManagerInterface $chainManager,
|
|
private JobChainExecutionCoordinator $coordinator,
|
|
private DependencyResolutionEngine $resolutionEngine
|
|
) {}
|
|
|
|
#[ConsoleCommand(name: 'queue:chain:create', description: 'Create a new job chain')]
|
|
public function createChain(
|
|
string $chainId,
|
|
string $name,
|
|
string $jobIds,
|
|
string $executionMode = 'sequential',
|
|
bool $stopOnFailure = true
|
|
): void {
|
|
$jobIdArray = array_map('trim', explode(',', $jobIds));
|
|
$mode = ChainExecutionMode::from($executionMode);
|
|
|
|
$jobChain = JobChain::create(
|
|
chainId: $chainId,
|
|
name: $name,
|
|
jobIds: $jobIdArray,
|
|
executionMode: $mode,
|
|
stopOnFailure: $stopOnFailure
|
|
);
|
|
|
|
$this->chainManager->createChain($jobChain);
|
|
|
|
echo "✅ Job chain created: {$name} ({$chainId})\n";
|
|
echo " Jobs: " . implode(' → ', $jobIdArray) . "\n";
|
|
echo " Mode: {$executionMode}\n";
|
|
echo " Stop on failure: " . ($stopOnFailure ? 'Yes' : 'No') . "\n";
|
|
}
|
|
|
|
#[ConsoleCommand(name: 'queue:chain:start', description: 'Start execution of a job chain')]
|
|
public function startChain(string $chainId): void
|
|
{
|
|
try {
|
|
$this->coordinator->startChainExecution($chainId);
|
|
echo "🚀 Chain execution started: {$chainId}\n";
|
|
} catch (\Exception $e) {
|
|
echo "❌ Failed to start chain: {$e->getMessage()}\n";
|
|
}
|
|
}
|
|
|
|
#[ConsoleCommand(name: 'queue:chain:status', description: 'Get status of a job chain')]
|
|
public function getChainStatus(string $chainId): void
|
|
{
|
|
try {
|
|
$status = $this->coordinator->getChainExecutionStatus($chainId);
|
|
|
|
echo "📋 Chain Status: {$status['name']} ({$chainId})\n\n";
|
|
|
|
$statusIcon = match($status['status']) {
|
|
'pending' => '⏳',
|
|
'running' => '🔄',
|
|
'completed' => '✅',
|
|
'failed' => '❌',
|
|
default => '❓'
|
|
};
|
|
|
|
echo " Status: {$statusIcon} {$status['status']}\n";
|
|
echo " Execution Mode: {$status['execution_mode']}\n";
|
|
echo " Stop on Failure: " . ($status['progress']['stop_on_failure'] ? 'Yes' : 'No') . "\n";
|
|
|
|
if ($status['started_at']) {
|
|
echo " Started: {$status['started_at']}\n";
|
|
}
|
|
|
|
if ($status['completed_at']) {
|
|
echo " Completed: {$status['completed_at']}\n";
|
|
}
|
|
|
|
echo "\n📊 Progress:\n";
|
|
echo " {$status['progress']['completed_jobs']}/{$status['progress']['total_jobs']} jobs completed ({$status['progress']['percentage']}%)\n\n";
|
|
|
|
echo "🔗 Job Status:\n";
|
|
foreach ($status['job_statuses'] as $jobStatus) {
|
|
$canExecute = $jobStatus['can_execute'] ? '✅' : '⏳';
|
|
$depStatus = "{$jobStatus['dependencies_satisfied']}/{$jobStatus['dependencies_total']} deps";
|
|
$position = $jobStatus['position'] + 1;
|
|
|
|
echo " {$canExecute} Job {$position}: {$jobStatus['job_id']} ({$depStatus})\n";
|
|
}
|
|
|
|
} catch (\Exception $e) {
|
|
echo "❌ Failed to get chain status: {$e->getMessage()}\n";
|
|
}
|
|
}
|
|
|
|
#[ConsoleCommand(name: 'queue:chain:list', description: 'List job chains by status')]
|
|
public function listChains(string $status = 'all'): void
|
|
{
|
|
$chains = match($status) {
|
|
'active' => $this->chainManager->getActiveChains(),
|
|
'pending' => $this->chainManager->getPendingChains(),
|
|
default => array_merge(
|
|
$this->chainManager->getPendingChains(),
|
|
$this->chainManager->getActiveChains()
|
|
)
|
|
};
|
|
|
|
echo "📋 Job Chains ({$status}):\n\n";
|
|
|
|
if (empty($chains)) {
|
|
echo " No chains found\n";
|
|
return;
|
|
}
|
|
|
|
foreach ($chains as $chain) {
|
|
$statusIcon = match($chain->status) {
|
|
'pending' => '⏳',
|
|
'running' => '🔄',
|
|
'completed' => '✅',
|
|
'failed' => '❌',
|
|
default => '❓'
|
|
};
|
|
|
|
$jobCount = count($chain->getJobIdsArray());
|
|
echo " {$statusIcon} {$chain->name} ({$chain->chainId})\n";
|
|
echo " Status: {$chain->status} | Jobs: {$jobCount} | Mode: {$chain->executionMode}\n";
|
|
|
|
if ($chain->startedAt) {
|
|
echo " Started: {$chain->startedAt}\n";
|
|
}
|
|
|
|
echo "\n";
|
|
}
|
|
}
|
|
|
|
#[ConsoleCommand(name: 'queue:chain:analyze', description: 'Analyze job chains for a specific job')]
|
|
public function analyzeChains(string $jobId): void
|
|
{
|
|
$analysis = $this->resolutionEngine->analyzeChains($jobId);
|
|
|
|
echo "🔍 Chain Analysis for job: {$jobId}\n\n";
|
|
|
|
if (empty($analysis['chains'])) {
|
|
echo " Job is not part of any chains\n";
|
|
return;
|
|
}
|
|
|
|
echo "📊 Total chains: {$analysis['total_chains']}\n\n";
|
|
|
|
foreach ($analysis['chains'] as $chain) {
|
|
$statusIcon = match($chain['status']) {
|
|
'pending' => '⏳',
|
|
'running' => '🔄',
|
|
'completed' => '✅',
|
|
'failed' => '❌',
|
|
default => '❓'
|
|
};
|
|
|
|
echo "🔗 {$statusIcon} {$chain['name']} ({$chain['chain_id']})\n";
|
|
echo " Status: {$chain['status']}\n";
|
|
echo " Mode: {$chain['execution_mode']}\n";
|
|
$position = $chain['job_position'] + 1;
|
|
echo " Position: {$position}/{$chain['total_jobs']}\n";
|
|
|
|
if ($chain['next_job_after_current']) {
|
|
echo " Next job: {$chain['next_job_after_current']}\n";
|
|
}
|
|
|
|
echo " Progress: {$chain['progress']['completed_jobs']}/{$chain['progress']['total_jobs']} ({$chain['progress']['percentage']}%)\n\n";
|
|
}
|
|
}
|
|
|
|
#[ConsoleCommand(name: 'queue:chain:delete', description: 'Delete a job chain')]
|
|
public function deleteChain(string $chainId): void
|
|
{
|
|
$chain = $this->chainManager->getChain($chainId);
|
|
|
|
if (!$chain) {
|
|
echo "❌ Chain not found: {$chainId}\n";
|
|
return;
|
|
}
|
|
|
|
if ($chain->isRunning()) {
|
|
echo "❌ Cannot delete running chain. Chain must be completed or failed.\n";
|
|
return;
|
|
}
|
|
|
|
$this->chainManager->deleteChain($chainId);
|
|
echo "✅ Chain deleted: {$chainId}\n";
|
|
}
|
|
|
|
#[ConsoleCommand(name: 'queue:chain:cleanup', description: 'Clean up old completed chains')]
|
|
public function cleanupOldChains(int $olderThanDays = 30): void
|
|
{
|
|
$deletedCount = $this->chainManager->cleanupOldChains($olderThanDays);
|
|
|
|
echo "🧹 Cleaned up old chains\n";
|
|
echo " Deleted: {$deletedCount} completed/failed chains older than {$olderThanDays} days\n";
|
|
}
|
|
|
|
#[ConsoleCommand(name: 'queue:chain:progress', description: 'Show detailed progress of a chain')]
|
|
public function showProgress(string $chainId): void
|
|
{
|
|
try {
|
|
$progress = $this->chainManager->getChainProgress($chainId);
|
|
|
|
echo "📊 Chain Progress: {$progress['name']} ({$chainId})\n\n";
|
|
|
|
$statusIcon = match($progress['status']) {
|
|
'pending' => '⏳',
|
|
'running' => '🔄',
|
|
'completed' => '✅',
|
|
'failed' => '❌',
|
|
default => '❓'
|
|
};
|
|
|
|
echo " Status: {$statusIcon} {$progress['status']}\n";
|
|
echo " Mode: {$progress['execution_mode']}\n";
|
|
echo " Progress: {$progress['completed_jobs']}/{$progress['total_jobs']} jobs ({$progress['percentage']}%)\n";
|
|
|
|
if ($progress['started_at']) {
|
|
echo " Started: {$progress['started_at']}\n";
|
|
}
|
|
|
|
if ($progress['completed_at']) {
|
|
echo " Completed: {$progress['completed_at']}\n";
|
|
}
|
|
|
|
// Progress bar
|
|
$barLength = 30;
|
|
$filledLength = (int) (($progress['percentage'] / 100) * $barLength);
|
|
$bar = str_repeat('█', $filledLength) . str_repeat('░', $barLength - $filledLength);
|
|
echo "\n [{$bar}] {$progress['percentage']}%\n";
|
|
|
|
} catch (\Exception $e) {
|
|
echo "❌ Failed to get chain progress: {$e->getMessage()}\n";
|
|
}
|
|
}
|
|
} |