66use Pantheon \Terminus \Site \SiteAwareInterface ;
77use Pantheon \Terminus \Site \SiteAwareTrait ;
88use Pantheon \Terminus \Exceptions \TerminusException ;
9+ use Pantheon \Terminus \Request \RequestAwareInterface ;
10+ use Pantheon \Terminus \Request \RequestAwareTrait ;
911
1012/**
1113 * Class AwaitCommand.
1214 *
1315 * @package Pantheon\Terminus\Commands\Workflow
1416 */
15- class WaitCommand extends TerminusCommand implements SiteAwareInterface
17+ class WaitCommand extends TerminusCommand implements SiteAwareInterface, RequestAwareInterface
1618{
1719 use SiteAwareTrait;
20+ use RequestAwareTrait;
1821
1922 /**
2023 * Wait for a workflow to complete. Usually this will be used to wait
@@ -28,13 +31,15 @@ class WaitCommand extends TerminusCommand implements SiteAwareInterface
2831 * @param $site_env_id The pantheon site to wait for.
2932 * @param $description The workflow description to wait for. Optional; default is code sync.
3033 * @option start Ignore any workflows started prior to the start time (epoch)
34+ * @option commit Commit sha to wait for
3135 * @option max Maximum number of seconds to wait for the workflow to complete
3236 */
3337 public function workflowWait (
3438 $ site_env_id ,
3539 $ description = '' ,
3640 $ options = [
3741 'start ' => 0 ,
42+ 'commit ' => '' ,
3843 'max ' => 180 ,
3944 ]
4045 ) {
@@ -65,10 +70,12 @@ public function workflowWait(
6570 if (!$ startTime ) {
6671 $ startTime = time () - 60 ;
6772 }
68- // if (!empty($options['commit'])) {
69- // $this->waitForCommit($startTime, $site, $env_name, $options['commit'], $options['max']);
70- // return;
71- // }
73+
74+ if (!empty ($ options ['commit ' ])) {
75+ $ this ->waitForCommit ($ startTime , $ site , $ env_name , $ options ['commit ' ], $ options ['max ' ]);
76+ return ;
77+ }
78+
7279 $ this ->waitForWorkflow ($ startTime , $ site , $ env_name , $ description , $ options ['max ' ]);
7380 }
7481
@@ -175,69 +182,170 @@ protected function waitForWorkflow(
175182 /**
176183 * Wait for a workflow with a given commit to complete.
177184 */
178- // public function waitForCommit(
179- // $startTime,
180- // $site,
181- // $env_name,
182- // $target_commit,
183- // $maxWaitInSeconds = 180,
184- // $maxNotFoundAttempts = null
185- // ) {
186- // $wfl = null;
187- // $wflc = $site->getWorkflowLogs();
188- // if (!$wflc instanceof WorkflowLogsCollection) {
189- // throw new TerminusException(
190- // 'Workflow logs could not be retrieved for site: {site}',
191- // ['site' => $site->id,]
192- // );
193- // }
194-
195- // // Remove workflows that are not for the environment $env_name.
196- // $wflc->filterForEnvironment($env_name);
197-
198- // // Find the latest workflow that matches the commit hash
199- // $wfl = $wflc->findLatestFromOptionsArray([
200- // 'target_commit' => $target_commit,
201- // ]);
202-
203- // $current_time = time();
204- // if ($maxWaitInSeconds > 0) {
205- // $end_time = $current_time + $maxWaitInSeconds;
206- // } else {
207- // $end_time = 0;
208- // }
209-
210- // // If we didn't find a workflow, then we need to wait for one to be created
211- // if (!$wfl instanceof WorkflowLog) {
212- // // sleep to give the workflow time to be created
213- // sleep($this->getConfig()->get('refresh_workflow_delay', 30));
214- // $wfl = $wflc->fetch()->findLatestFromOptionsArray([
215- // 'target_commit' => $target_commit,
216- // ]);
217- // $current_time = time();
218- // if ($end_time > 0 && $current_time >= $end_time) {
219- // throw new TerminusException(
220- // 'Exceeded maximum wait time of {max} seconds.',
221- // ['max' => $maxWaitInSeconds]
222- // );
223- // }
224- // }
225-
226- // while (!$wfl->isFinished()) {
227- // $current_time = time();
228- // if ($end_time > 0 && $current_time >= $end_time) {
229- // throw new TerminusException(
230- // 'Exceeded maximum wait time of {max} seconds.',
231- // ['max' => $maxWaitInSeconds]
232- // );
233- // }
234- // $this->log()->notice('Waiting for workflow {id} to complete.', ['id' => $wfl->id,]);
235- // sleep($this->getConfig()->get('refresh_workflow_delay', 30));
236- // $wfl->fetch();
237- // }
238- // $this->log()->notice('Workflow {id} has completed with status {status}.', [
239- // 'id' => $wfl->id,
240- // 'status' => $wfl->get('status'),
241- // ]);
242- // }
185+ protected function waitForCommit (
186+ $ startTime ,
187+ $ site ,
188+ $ env_name ,
189+ $ target_commit ,
190+ $ maxWaitInSeconds = 180
191+ ) {
192+ $ current_time = time ();
193+ if ($ maxWaitInSeconds > 0 ) {
194+ $ end_time = $ current_time + $ maxWaitInSeconds ;
195+ } else {
196+ $ end_time = 0 ;
197+ }
198+
199+ // Validate commit SHA format
200+ if (!preg_match ('/^[0-9a-f]{40}$/ ' , $ target_commit )) {
201+ throw new TerminusException (
202+ 'Commit {commit} is not a valid commit SHA. ' ,
203+ ['commit ' => $ target_commit ]
204+ );
205+ }
206+
207+ $ this ->log ()->notice ('Waiting for workflow with commit {commit} on environment {env}. ' , [
208+ 'commit ' => $ target_commit ,
209+ 'env ' => $ env_name
210+ ]);
211+
212+
213+ $ workflow = null ;
214+ $ retry_count = 0 ;
215+ $ max_retries = 10 ;
216+
217+ do {
218+ $ current_time = time ();
219+
220+ // Check timeout
221+ if ($ end_time > 0 && $ current_time >= $ end_time ) {
222+ throw new TerminusException (
223+ 'Workflow with commit {commit} timed out after {timeout} seconds. ' ,
224+ ['commit ' => $ target_commit , 'timeout ' => $ maxWaitInSeconds ]
225+ );
226+ }
227+
228+ // Fetch workflow logs using the logs/workflows endpoint
229+ $ response = $ this ->request ()->request ("sites/ {$ site ->id }/logs/workflows " );
230+ $ workflow_logs = $ response ['data ' ] ?? [];
231+
232+ $ this ->log ()->debug ('Found {count} total workflow logs ' , ['count ' => count ($ workflow_logs )]);
233+
234+ // Filter for the target environment and commit
235+ $ matching_workflows = [];
236+
237+ foreach ($ workflow_logs as $ log ) {
238+ // Check if this workflow is for the target environment
239+ if (isset ($ log ->workflow ->environment ) && $ log ->workflow ->environment === $ env_name ) {
240+ // Check if this workflow has the target commit
241+ if (isset ($ log ->workflow ->target_commit ) && $ log ->workflow ->target_commit === $ target_commit ) {
242+ // Check if workflow started after our start time
243+ if (isset ($ log ->workflow ->started_at ) && $ log ->workflow ->started_at >= $ startTime ) {
244+ $ matching_workflows [] = $ log ;
245+ }
246+ }
247+ }
248+ }
249+
250+ $ this ->log ()->debug ('Found {count} matching workflows for commit {commit} on env {env} ' , [
251+ 'count ' => count ($ matching_workflows ),
252+ 'commit ' => $ target_commit ,
253+ 'env ' => $ env_name
254+ ]);
255+
256+ // Find the most recent matching workflow
257+ if (!empty ($ matching_workflows )) {
258+ // Sort by started_at descending to get the most recent
259+ usort ($ matching_workflows , function ($ a , $ b ) {
260+ return $ b ->workflow ->started_at <=> $ a ->workflow ->started_at ;
261+ });
262+
263+ $ workflow = $ matching_workflows [0 ];
264+ $ this ->log ()->notice ('Found workflow {id} with description "{description}" for commit {commit} ' , [
265+ 'id ' => $ workflow ->workflow ->id ,
266+ 'description ' => $ workflow ->workflow ->description ?? 'N/A ' ,
267+ 'commit ' => $ target_commit
268+ ]);
269+ break ;
270+ }
271+
272+ $ retry_count ++;
273+ if ($ retry_count >= $ max_retries ) {
274+ throw new TerminusException (
275+ 'Workflow with commit {commit} not found after {retries} attempts. ' ,
276+ ['commit ' => $ target_commit , 'retries ' => $ max_retries ]
277+ );
278+ }
279+
280+ $ this ->log ()->debug ('Workflow not found, retrying... ({retry}/{max}) ' , [
281+ 'retry ' => $ retry_count ,
282+ 'max ' => $ max_retries
283+ ]);
284+ sleep (5 );
285+
286+ } while (!$ workflow );
287+
288+ // Now wait for the workflow to complete
289+ $ this ->log ()->notice ('Waiting for workflow {id} to complete... ' , ['id ' => $ workflow ->workflow ->id ]);
290+
291+ $ retry_interval = $ this ->getConfig ()->get ('workflow_polling_delay_ms ' , 5000 );
292+ if ($ retry_interval < 1000 ) {
293+ $ retry_interval = 1000 ;
294+ }
295+
296+ do {
297+ $ current_time = time ();
298+ if ($ end_time > 0 && $ current_time >= $ end_time ) {
299+ throw new TerminusException (
300+ 'Workflow timed out after {timeout} seconds. ' ,
301+ ['timeout ' => $ maxWaitInSeconds ]
302+ );
303+ }
304+
305+ // Re-fetch workflow logs to get updated status
306+ $ response = $ this ->request ()->request ("sites/ {$ site ->id }/logs/workflows " );
307+ $ workflow_logs = $ response ['data ' ] ?? [];
308+
309+ // Find our specific workflow
310+ $ updated_workflow = null ;
311+ foreach ($ workflow_logs as $ log ) {
312+ if ($ log ->workflow ->id === $ workflow ->workflow ->id ) {
313+ $ updated_workflow = $ log ;
314+ break ;
315+ }
316+ }
317+
318+ if (!$ updated_workflow ) {
319+ throw new TerminusException ('Workflow {id} disappeared during execution. ' , ['id ' => $ workflow ->workflow ->id ]);
320+ }
321+
322+ $ workflow = $ updated_workflow ;
323+
324+ $ this ->log ()->debug ('Workflow {id} status: {status} ' , [
325+ 'id ' => $ workflow ->workflow ->id ,
326+ 'status ' => $ workflow ->workflow ->status ?? 'unknown '
327+ ]);
328+
329+ // Check if workflow is finished
330+ if (isset ($ workflow ->workflow ->status ) && in_array ($ workflow ->workflow ->status , ['Success ' , 'Failed ' , 'Aborted ' ])) {
331+ break ;
332+ }
333+
334+ usleep ($ retry_interval * 1000 );
335+
336+ } while (true );
337+
338+ // Check if workflow succeeded
339+ if ($ workflow ->workflow ->status !== 'Success ' ) {
340+ throw new TerminusException (
341+ 'Workflow {id} failed with status: {status} ' ,
342+ ['id ' => $ workflow ->workflow ->id , 'status ' => $ workflow ->workflow ->status ]
343+ );
344+ }
345+
346+ $ this ->log ()->notice ('Workflow {id} completed successfully for commit {commit} ' , [
347+ 'id ' => $ workflow ->workflow ->id ,
348+ 'commit ' => $ target_commit
349+ ]);
350+ }
243351}
0 commit comments