From 6ee814b5f4cc00761fca3fb2037e8f8e0ae84287 Mon Sep 17 00:00:00 2001 From: Greg Anderson Date: Tue, 3 Mar 2026 19:34:08 -0800 Subject: [PATCH] Extract waitForCommit into reusable WaitForCommit utility class Move the commit-waiting logic from WaitCommand into a static method in Helpers\Utility\WaitForCommit so it can be called from outside the command context. The WaitCommand now delegates to the utility, passing in the Request and Logger dependencies explicitly. --- src/Commands/Workflow/WaitCommand.php | 181 ++-------------------- src/Helpers/Utility/WaitForCommit.php | 207 ++++++++++++++++++++++++++ 2 files changed, 220 insertions(+), 168 deletions(-) create mode 100644 src/Helpers/Utility/WaitForCommit.php diff --git a/src/Commands/Workflow/WaitCommand.php b/src/Commands/Workflow/WaitCommand.php index 821699c9c..6567da0b4 100644 --- a/src/Commands/Workflow/WaitCommand.php +++ b/src/Commands/Workflow/WaitCommand.php @@ -3,6 +3,7 @@ namespace Pantheon\Terminus\Commands\Workflow; use Pantheon\Terminus\Commands\TerminusCommand; +use Pantheon\Terminus\Helpers\Utility\WaitForCommit; use Pantheon\Terminus\Site\SiteAwareInterface; use Pantheon\Terminus\Site\SiteAwareTrait; use Pantheon\Terminus\Exceptions\TerminusException; @@ -199,173 +200,17 @@ protected function waitForCommit( $target_commit, $maxWaitInSeconds = 180 ) { - $current_time = time(); - if ($maxWaitInSeconds > 0) { - $end_time = $current_time + $maxWaitInSeconds; - } else { - $end_time = 0; - } - - // Validate commit SHA format (allow shortened hashes of 7+ characters) - if (!preg_match('/^[0-9a-f]{7,40}$/', $target_commit)) { - throw new TerminusException( - 'Commit {commit} is not a valid commit SHA (must be 7-40 hexadecimal characters).', - ['commit' => $target_commit] - ); - } - - $this->log()->notice('Waiting for workflow with commit {commit} on environment {env}.', [ - 'commit' => $target_commit, - 'env' => $env_name - ]); - - - $workflow = null; - $retry_count = 0; - $max_retries = 10; - - do { - $current_time = time(); - - // Check timeout - if ($end_time > 0 && $current_time >= $end_time) { - $this->log()->warning( - 'Waited \'{timeout}\' seconds, giving up waiting for workflow with commit {commit} to finish', - ['commit' => $target_commit, 'timeout' => $maxWaitInSeconds] - ); - return; - } - - // Fetch workflow logs using the logs/workflows endpoint - $response = $this->request()->request("sites/{$site->id}/logs/workflows"); - $workflow_logs = $response['data'] ?? []; - - $this->log()->debug('Found {count} total workflow logs', ['count' => count($workflow_logs)]); - - // Filter for the target environment and commit - $matching_workflows = []; - - foreach ($workflow_logs as $log) { - // Check if this workflow is for the target environment - if (isset($log->workflow->environment) && $log->workflow->environment === $env_name) { - // Check if this workflow has the target commit (support shortened hashes) - if ( - isset($log->workflow->target_commit) && - strpos($log->workflow->target_commit, $target_commit) === 0 - ) { - // Check if workflow started after our start time - if (isset($log->workflow->started_at) && $log->workflow->started_at >= $startTime) { - $matching_workflows[] = $log; - } - } - } - } - - $this->log()->debug('Found {count} matching workflows for commit {commit} on env {env}', [ - 'count' => count($matching_workflows), - 'commit' => $target_commit, - 'env' => $env_name - ]); - - // Find the most recent matching workflow - if (!empty($matching_workflows)) { - // Sort by started_at descending to get the most recent - usort($matching_workflows, function ($a, $b) { - return $b->workflow->started_at <=> $a->workflow->started_at; - }); - - $workflow = $matching_workflows[0]; - $this->log()->notice('Found workflow {id} with description "{description}" for commit {commit}', [ - 'id' => $workflow->workflow->id, - 'description' => $workflow->workflow->description ?? 'N/A', - 'commit' => $target_commit - ]); - break; - } - - $retry_count++; - if ($retry_count >= $max_retries) { - $this->log()->warning( - 'Workflow with commit {commit} not found after {retries} attempts.', - ['commit' => $target_commit, 'retries' => $max_retries] - ); - return; - } - - $this->log()->debug('Workflow not found, retrying... ({retry}/{max})', [ - 'retry' => $retry_count, - 'max' => $max_retries - ]); - sleep(5); - } while (!$workflow); - - // Now wait for the workflow to complete - $this->log()->notice('Waiting for workflow {id} to complete...', ['id' => $workflow->workflow->id]); - - $retry_interval = $this->getConfig()->get('workflow_polling_delay_ms', 5000); - if ($retry_interval < 1000) { - $retry_interval = 1000; - } - - do { - $current_time = time(); - if ($end_time > 0 && $current_time >= $end_time) { - $this->log()->warning( - 'Waited \'{timeout}\' seconds, giving up waiting for workflow to finish', - ['timeout' => $maxWaitInSeconds] - ); - return; - } - - // Re-fetch workflow logs to get updated status - $response = $this->request()->request("sites/{$site->id}/logs/workflows"); - $workflow_logs = $response['data'] ?? []; - - // Find our specific workflow - $updated_workflow = null; - foreach ($workflow_logs as $log) { - if ($log->workflow->id === $workflow->workflow->id) { - $updated_workflow = $log; - break; - } - } - - if (!$updated_workflow) { - throw new TerminusException( - 'Workflow {id} disappeared during execution.', - ['id' => $workflow->workflow->id] - ); - } - - $workflow = $updated_workflow; - - $this->log()->debug('Workflow {id} status: {status}', [ - 'id' => $workflow->workflow->id, - 'status' => $workflow->workflow->status ?? 'unknown' - ]); - - // Check if workflow is finished - if ( - isset($workflow->workflow->status) && - in_array($workflow->workflow->status, ['Success', 'Failed', 'Aborted']) - ) { - break; - } - - usleep($retry_interval * 1000); - } while (true); - - // Check if workflow succeeded - if ($workflow->workflow->status !== 'Success') { - throw new TerminusException( - 'Workflow {id} failed with status: {status}', - ['id' => $workflow->workflow->id, 'status' => $workflow->workflow->status] - ); - } - - $this->log()->notice('Workflow {id} completed successfully for commit {commit}', [ - 'id' => $workflow->workflow->id, - 'commit' => $target_commit - ]); + $pollingDelayMs = $this->getConfig()->get('workflow_polling_delay_ms', 5000); + + WaitForCommit::waitForCommit( + $startTime, + $site, + $env_name, + $target_commit, + $this->request(), + $this->log(), + $maxWaitInSeconds, + $pollingDelayMs + ); } } diff --git a/src/Helpers/Utility/WaitForCommit.php b/src/Helpers/Utility/WaitForCommit.php new file mode 100644 index 000000000..cbfe33a49 --- /dev/null +++ b/src/Helpers/Utility/WaitForCommit.php @@ -0,0 +1,207 @@ + 0) { + $end_time = $current_time + $maxWaitInSeconds; + } else { + $end_time = 0; + } + + // Validate commit SHA format (allow shortened hashes of 7+ characters) + if (!preg_match('/^[0-9a-f]{7,40}$/', $target_commit)) { + throw new TerminusException( + 'Commit {commit} is not a valid commit SHA (must be 7-40 hexadecimal characters).', + ['commit' => $target_commit] + ); + } + + $logger->notice('Waiting for workflow with commit {commit} on environment {env}.', [ + 'commit' => $target_commit, + 'env' => $env_name + ]); + + $workflow = null; + $retry_count = 0; + $max_retries = 10; + + do { + $current_time = time(); + + // Check timeout + if ($end_time > 0 && $current_time >= $end_time) { + $logger->warning( + 'Waited \'{timeout}\' seconds, giving up waiting for workflow with commit {commit} to finish', + ['commit' => $target_commit, 'timeout' => $maxWaitInSeconds] + ); + return; + } + + // Fetch workflow logs using the logs/workflows endpoint + $response = $request->request("sites/{$site->id}/logs/workflows"); + $workflow_logs = $response['data'] ?? []; + + $logger->debug('Found {count} total workflow logs', ['count' => count($workflow_logs)]); + + // Filter for the target environment and commit + $matching_workflows = []; + + foreach ($workflow_logs as $log) { + // Check if this workflow is for the target environment + if (isset($log->workflow->environment) && $log->workflow->environment === $env_name) { + // Check if this workflow has the target commit (support shortened hashes) + if ( + isset($log->workflow->target_commit) && + strpos($log->workflow->target_commit, $target_commit) === 0 + ) { + // Check if workflow started after our start time + if (isset($log->workflow->started_at) && $log->workflow->started_at >= $startTime) { + $matching_workflows[] = $log; + } + } + } + } + + $logger->debug('Found {count} matching workflows for commit {commit} on env {env}', [ + 'count' => count($matching_workflows), + 'commit' => $target_commit, + 'env' => $env_name + ]); + + // Find the most recent matching workflow + if (!empty($matching_workflows)) { + // Sort by started_at descending to get the most recent + usort($matching_workflows, function ($a, $b) { + return $b->workflow->started_at <=> $a->workflow->started_at; + }); + + $workflow = $matching_workflows[0]; + $logger->notice('Found workflow {id} with description "{description}" for commit {commit}', [ + 'id' => $workflow->workflow->id, + 'description' => $workflow->workflow->description ?? 'N/A', + 'commit' => $target_commit + ]); + break; + } + + $retry_count++; + if ($retry_count >= $max_retries) { + $logger->warning( + 'Workflow with commit {commit} not found after {retries} attempts.', + ['commit' => $target_commit, 'retries' => $max_retries] + ); + return; + } + + $logger->debug('Workflow not found, retrying... ({retry}/{max})', [ + 'retry' => $retry_count, + 'max' => $max_retries + ]); + sleep(5); + } while (!$workflow); + + // Now wait for the workflow to complete + $logger->notice('Waiting for workflow {id} to complete...', ['id' => $workflow->workflow->id]); + + $retry_interval = $pollingDelayMs; + if ($retry_interval < 1000) { + // The API will not allow polling faster than once per second. + $retry_interval = 1000; + } + + do { + $current_time = time(); + if ($end_time > 0 && $current_time >= $end_time) { + $logger->warning( + 'Waited \'{timeout}\' seconds, giving up waiting for workflow to finish', + ['timeout' => $maxWaitInSeconds] + ); + return; + } + + // Re-fetch workflow logs to get updated status + $response = $request->request("sites/{$site->id}/logs/workflows"); + $workflow_logs = $response['data'] ?? []; + + // Find our specific workflow + $updated_workflow = null; + foreach ($workflow_logs as $log) { + if ($log->workflow->id === $workflow->workflow->id) { + $updated_workflow = $log; + break; + } + } + + if (!$updated_workflow) { + throw new TerminusException( + 'Workflow {id} disappeared during execution.', + ['id' => $workflow->workflow->id] + ); + } + + $workflow = $updated_workflow; + + $logger->debug('Workflow {id} status: {status}', [ + 'id' => $workflow->workflow->id, + 'status' => $workflow->workflow->status ?? 'unknown' + ]); + + // Check if workflow is finished + if ( + isset($workflow->workflow->status) && + in_array($workflow->workflow->status, ['Success', 'Failed', 'Aborted']) + ) { + break; + } + + usleep($retry_interval * 1000); + } while (true); + + // Check if workflow succeeded + if ($workflow->workflow->status !== 'Success') { + throw new TerminusException( + 'Workflow {id} failed with status: {status}', + ['id' => $workflow->workflow->id, 'status' => $workflow->workflow->status] + ); + } + + $logger->notice('Workflow {id} completed successfully for commit {commit}', [ + 'id' => $workflow->workflow->id, + 'commit' => $target_commit + ]); + } +}