Skip to content

Commit 6cf1f06

Browse files
committed
Improve regex matching and resillence
1 parent 3942602 commit 6cf1f06

File tree

2 files changed

+150
-5
lines changed

2 files changed

+150
-5
lines changed

modules/python/terraform/extract_terraform_operation_metadata.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@
1515
# Regex to extract: elapsed time from "Still creating/destroying..." lines
1616
# e.g. module.azapi[...].azapi_resource.aks_cluster: Still creating... [29m51s elapsed]
1717
# or: module.azapi[...].azapi_resource.aks_cluster: Still destroying... 00m40s elapsed]
18-
ELAPSED_PATTERN = re.compile(r"Still (?:creating|destroying)\.\.\. \[?(\d+h\d+m\d+s|\d+h\d+s|\d+m\d+s|\d+s) elapsed\]")
18+
ELAPSED_PATTERN = re.compile(r"Still (?:creating|destroying).*?(\d+h\d+m\d+s|\d+h\d+s|\d+m\d+s|\d+s) elapsed")
1919

2020
def time_to_seconds(time_str):
2121
try:
@@ -90,8 +90,8 @@ def process_terraform_logs(log_path, _command_type, _scenario_type, _scenario_na
9090
start_match = START_PATTERN.search(line)
9191
if start_match:
9292
# A new run is starting; flush the previous run if it was incomplete
93-
if current_full_path and not current_completed and current_elapsed_time_str:
94-
results.append(build_result(current_full_path, current_elapsed_time_str, run_id, _command_type, _scenario_type, _scenario_name, False, current_timed_out))
93+
if current_full_path and not current_completed:
94+
results.append(build_result(current_full_path, current_elapsed_time_str or "0s", run_id, _command_type, _scenario_type, _scenario_name, False, current_timed_out))
9595

9696
current_full_path = start_match.group(1)
9797
current_elapsed_time_str = None
@@ -112,8 +112,8 @@ def process_terraform_logs(log_path, _command_type, _scenario_type, _scenario_na
112112
continue
113113

114114
# Flush the last run if it was incomplete
115-
if current_full_path and not current_completed and current_elapsed_time_str:
116-
results.append(build_result(current_full_path, current_elapsed_time_str, run_id, _command_type, _scenario_type, _scenario_name, False, current_timed_out))
115+
if current_full_path and not current_completed:
116+
results.append(build_result(current_full_path, current_elapsed_time_str or "0s", run_id, _command_type, _scenario_type, _scenario_name, False, current_timed_out))
117117
except Exception as e:
118118
print(f"[ERROR] Failed to process log file '{log_file}': {e}")
119119

modules/python/tests/test_extract_terraform_operation_metadata.py

Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,5 +306,150 @@ def test_process_terraform_logs_with_all_retries_failed(self, mock_open_file, mo
306306
mock_open_file.assert_called_once_with('/fake/path/terraform_apply.log', 'r', encoding='utf-8')
307307
mock_isfile.assert_called_once_with("/fake/path/terraform_apply.log")
308308

309+
@patch("os.path.isfile", return_value=True)
310+
@patch("builtins.open", new_callable=mock_open, read_data=(
311+
'module.azapi["ccp-provisioning-H2"].azapi_resource.aks_cluster: Destroying... [id=/subscriptions/b8ceb4e5-f05b-4562-a9f5-14acb1f24219/resourceGroups/59393-51f48219/providers/Microsoft.ContainerService/managedClusters/ccp-provisioning-H2]\n'
312+
'module.azapi["ccp-provisioning-H2"].azapi_resource.aks_cluster: Still destroying... [id=/subscriptions/b8ceb4e5-f05b-4562-a9f5-...ce/managedClusters/ccp-provisioning-H2, 00m10s elapsed]\n'
313+
))
314+
def test_process_terraform_logs_still_destroying_with_id_prefix(self, mock_open_file, mock_isfile):
315+
"""Verify metadata record is created when 'Still destroying' line contains id= prefix before elapsed time."""
316+
os.environ["RUN_ID"] = "4455667788"
317+
318+
results = process_terraform_logs(
319+
log_path="/fake/path",
320+
_command_type="destroy",
321+
_scenario_type="ccp",
322+
_scenario_name="provisioning",
323+
)
324+
325+
self.assertEqual(len(results), 1)
326+
self.assertEqual(results[0]["run_id"], "4455667788")
327+
self.assertEqual(results[0]["module_name"], 'azapi["ccp-provisioning-H2"]')
328+
self.assertEqual(results[0]["submodule_name"], "azapi_resource")
329+
self.assertEqual(results[0]["resource_name"], "aks_cluster")
330+
self.assertEqual(results[0]["action"], "destroy")
331+
self.assertEqual(results[0]["time_taken_seconds"], 10)
332+
self.assertEqual(results[0]["result"], {"success": False, "timed_out": False})
333+
mock_open_file.assert_called_once_with('/fake/path/terraform_destroy.log', 'r', encoding='utf-8')
334+
mock_isfile.assert_called_once_with("/fake/path/terraform_destroy.log")
335+
336+
@patch("os.path.isfile", return_value=True)
337+
@patch("builtins.open", new_callable=mock_open, read_data=(
338+
'module.azapi["ccp-provisioning-H2"].azapi_resource.aks_cluster: Creating... [id=/subscriptions/b8ceb4e5-f05b-4562-a9f5-14acb1f24219/resourceGroups/59393-51f48219/providers/Microsoft.ContainerService/managedClusters/ccp-provisioning-H2]\n'
339+
'module.azapi["ccp-provisioning-H2"].azapi_resource.aks_cluster: Still creating... [id=/subscriptions/b8ceb4e5-f05b-4562-a9f5-...ce/managedClusters/ccp-provisioning-H2, 10m0s elapsed]\n'
340+
'module.azapi["ccp-provisioning-H2"].azapi_resource.aks_cluster: Still creating... [id=/subscriptions/b8ceb4e5-f05b-4562-a9f5-...ce/managedClusters/ccp-provisioning-H2, 20m0s elapsed]\n'
341+
'module.azapi["ccp-provisioning-H2"].azapi_resource.aks_cluster: Creation complete after 25m30s [id=/subscriptions/b8ceb4e5-f05b-4562-a9f5-14acb1f24219/resourceGroups/59393-51f48219/providers/Microsoft.ContainerService/managedClusters/ccp-provisioning-H2]\n'
342+
))
343+
def test_process_terraform_logs_still_creating_with_id_prefix_then_complete(self, mock_open_file, mock_isfile):
344+
"""Verify that 'Still creating' lines with id= prefix are parsed and final completion record is used."""
345+
os.environ["RUN_ID"] = "5566778899"
346+
347+
results = process_terraform_logs(
348+
log_path="/fake/path",
349+
_command_type="apply",
350+
_scenario_type="ccp",
351+
_scenario_name="provisioning",
352+
)
353+
354+
self.assertEqual(len(results), 1)
355+
self.assertEqual(results[0]["run_id"], "5566778899")
356+
self.assertEqual(results[0]["module_name"], 'azapi["ccp-provisioning-H2"]')
357+
self.assertEqual(results[0]["submodule_name"], "azapi_resource")
358+
self.assertEqual(results[0]["resource_name"], "aks_cluster")
359+
self.assertEqual(results[0]["action"], "apply")
360+
self.assertEqual(results[0]["time_taken_seconds"], 1530)
361+
self.assertEqual(results[0]["result"], {"success": True, "timed_out": False})
362+
mock_open_file.assert_called_once_with('/fake/path/terraform_apply.log', 'r', encoding='utf-8')
363+
mock_isfile.assert_called_once_with("/fake/path/terraform_apply.log")
364+
365+
@patch("os.path.isfile", return_value=True)
366+
@patch("builtins.open", new_callable=mock_open, read_data=(
367+
'module.azapi["ccp"].azapi_resource.aks_cluster: Destroying... [id=/subscriptions/b8ceb4e5/resourceGroups/59393/providers/Microsoft.ContainerService/managedClusters/ccp-H2]\n'
368+
'│ Error: deleting Resource: unexpected status 409\n'
369+
))
370+
def test_process_terraform_logs_failure_without_elapsed_line(self, mock_open_file, mock_isfile):
371+
"""Verify a failure record with 0s elapsed is created when there is no 'Still destroying' line."""
372+
os.environ["RUN_ID"] = "6677889900"
373+
374+
results = process_terraform_logs(
375+
log_path="/fake/path",
376+
_command_type="destroy",
377+
_scenario_type="ccp",
378+
_scenario_name="provisioning",
379+
)
380+
381+
self.assertEqual(len(results), 1)
382+
self.assertEqual(results[0]["run_id"], "6677889900")
383+
self.assertEqual(results[0]["module_name"], 'azapi["ccp"]')
384+
self.assertEqual(results[0]["submodule_name"], "azapi_resource")
385+
self.assertEqual(results[0]["resource_name"], "aks_cluster")
386+
self.assertEqual(results[0]["action"], "destroy")
387+
self.assertEqual(results[0]["time_taken_seconds"], 0)
388+
self.assertEqual(results[0]["result"], {"success": False, "timed_out": False})
389+
mock_open_file.assert_called_once_with('/fake/path/terraform_destroy.log', 'r', encoding='utf-8')
390+
mock_isfile.assert_called_once_with("/fake/path/terraform_destroy.log")
391+
392+
@patch("os.path.isfile", return_value=True)
393+
@patch("builtins.open", new_callable=mock_open, read_data=(
394+
'module.azapi["ccp"].azapi_resource.aks_cluster: Creating...\n'
395+
'│ Error: creating Resource: context deadline exceeded\n'
396+
))
397+
def test_process_terraform_logs_timeout_without_elapsed_line(self, mock_open_file, mock_isfile):
398+
"""Verify a failure record with timed_out=True and 0s elapsed is created when timeout occurs without elapsed lines."""
399+
os.environ["RUN_ID"] = "7788990011"
400+
401+
results = process_terraform_logs(
402+
log_path="/fake/path",
403+
_command_type="apply",
404+
_scenario_type="ccp",
405+
_scenario_name="provisioning",
406+
)
407+
408+
self.assertEqual(len(results), 1)
409+
self.assertEqual(results[0]["run_id"], "7788990011")
410+
self.assertEqual(results[0]["module_name"], 'azapi["ccp"]')
411+
self.assertEqual(results[0]["resource_name"], "aks_cluster")
412+
self.assertEqual(results[0]["action"], "apply")
413+
self.assertEqual(results[0]["time_taken_seconds"], 0)
414+
self.assertEqual(results[0]["result"], {"success": False, "timed_out": True})
415+
mock_open_file.assert_called_once_with('/fake/path/terraform_apply.log', 'r', encoding='utf-8')
416+
mock_isfile.assert_called_once_with("/fake/path/terraform_apply.log")
417+
418+
@patch("os.path.isfile", return_value=True)
419+
@patch("builtins.open", new_callable=mock_open, read_data=(
420+
'module.azapi["ccp"].azapi_resource.aks_cluster: Creating...\n'
421+
'│ Error: creating Resource: unexpected status 409\n'
422+
'module.azapi["ccp"].azapi_resource.aks_cluster: Creating...\n'
423+
'module.azapi["ccp"].azapi_resource.aks_cluster: Still creating... [5m0s elapsed]\n'
424+
'module.azapi["ccp"].azapi_resource.aks_cluster: Creation complete after 8m30s [id=/subscriptions/b8ceb4e5/resourceGroups/59393/providers/Microsoft.ContainerService/managedClusters/ccp-H2]\n'
425+
))
426+
def test_process_terraform_logs_retry_after_immediate_failure(self, mock_open_file, mock_isfile):
427+
"""Verify first run records 0s failure when it fails immediately, and retry succeeds normally."""
428+
os.environ["RUN_ID"] = "8899001122"
429+
430+
results = process_terraform_logs(
431+
log_path="/fake/path",
432+
_command_type="apply",
433+
_scenario_type="ccp",
434+
_scenario_name="provisioning",
435+
)
436+
437+
self.assertEqual(len(results), 2)
438+
439+
# First run failed immediately - no elapsed time
440+
self.assertEqual(results[0]["module_name"], 'azapi["ccp"]')
441+
self.assertEqual(results[0]["resource_name"], "aks_cluster")
442+
self.assertEqual(results[0]["time_taken_seconds"], 0)
443+
self.assertEqual(results[0]["result"], {"success": False, "timed_out": False})
444+
445+
# Retry succeeded
446+
self.assertEqual(results[1]["module_name"], 'azapi["ccp"]')
447+
self.assertEqual(results[1]["resource_name"], "aks_cluster")
448+
self.assertEqual(results[1]["time_taken_seconds"], 510)
449+
self.assertEqual(results[1]["result"], {"success": True, "timed_out": False})
450+
451+
mock_open_file.assert_called_once_with('/fake/path/terraform_apply.log', 'r', encoding='utf-8')
452+
mock_isfile.assert_called_once_with("/fake/path/terraform_apply.log")
453+
309454
if __name__ == "__main__":
310455
unittest.main()

0 commit comments

Comments
 (0)