Skip to content

Commit 8a79bd4

Browse files
authored
fix(aws): skip CodeBuild unsupported/timeout regions (#2390)
### Type of change <!-- Mark the relevant option with an "x" --> - [x] Bug fix (non-breaking change that fixes an issue) ### Summary This PR hardens AWS CodeBuild sync so a single problematic region does not fail the entire AWS sync run. Changes: - Pre-filters CodeBuild regions by intersecting requested regions with `boto3` available CodeBuild regions across partitions. - Skips unsupported regions with an explicit log message. - Extends `aws_handle_regions` to treat regional endpoint timeout connectivity errors (`ConnectTimeoutError`, `ReadTimeoutError`) as skippable regional failures, similar to existing `EndpointConnectionError` behavior. - Adds/updates unit tests for unsupported-region skip, timeout skip behavior, and non-skippable error propagation. ### Related issues or links <!-- Include links to relevant issues or other pages. Use "Fixes #123" or "Closes #123" to auto-close issues. --> - Fixes #N/A ### Breaking changes <!-- If this PR introduces breaking changes, describe the impact and migration path. Otherwise, delete this section. --> None. ### How was this tested? <!-- Describe how you tested your changes. Include relevant details such as test configuration, commands run, or manual testing steps. --> ### Checklist #### General - [x] I have read the [contributing guidelines](https://cartography-cncf.github.io/cartography/dev/developer-guide.html). - [x] The linter passes locally (`make lint`). - [x] I have added/updated tests that prove my fix is effective or my feature works. #### Proof of functionality <!-- Provide at least one of the following to help reviewers verify your changes: --> - [ ] Screenshot showing the graph before and after changes. - [x] New or updated unit/integration tests. #### If you are adding or modifying a synced entity - [ ] Included Cartography sync logs from a real environment demonstrating successful synchronization of the new/modified entity. Logs should show: - The sync job starting and completing without errors - The number of nodes/relationships created or updated - Example: ``` INFO:cartography.intel.aws.ec2:Loading 42 EC2 instances for region us-east-1 INFO:cartography.intel.aws.ec2:Synced EC2 instances in 3.21 seconds ``` #### If you are changing a node or relationship - [ ] Updated the [schema documentation](https://github.com/cartography-cncf/cartography/tree/master/docs/root/modules). - [ ] Updated the [schema README](https://github.com/cartography-cncf/cartography/blob/master/docs/schema/README.md). #### If you are implementing a new intel module - [ ] Used the NodeSchema [data model](https://cartography-cncf.github.io/cartography/dev/writing-intel-modules.html#defining-a-node). ### Notes for reviewers <!-- Optional: Add any context that would help reviewers, such as areas to focus on, design decisions, or open questions. --> Root cause: - CodeBuild regional iteration could hit per-region endpoint/connectivity failures and fail the whole sync path. Why this is safe: - Scope is limited to region filtering and regional connectivity exceptions. - Non-skippable auth/config errors are still raised. Errors now skipped: - Unsupported CodeBuild regions (pre-filtered in CodeBuild sync). - `EndpointConnectionError` (existing behavior). - `ConnectTimeoutError` and `ReadTimeoutError` (new handling in regional wrapper). Errors still raised: - Non-skippable auth/config errors, e.g. `InvalidToken` and other unhandled `ClientError`s. Redacted incident logs (as observed): ```text botocore.exceptions.ConnectTimeoutError: Connect timeout on endpoint URL: "https://codebuild.ca-west-1.amazonaws.com/" urllib3.exceptions.ConnectTimeoutError: (<AWSHTTPSConnection(host='codebuild.ca-west-1.amazonaws.com', port=443)>, 'Connection to codebuild.ca-west-1.amazonaws.com timed out. (connect timeout=60)') ``` ```text botocore.exceptions.ClientError: An error occurred (AccessDenied) when calling the AssumeRole operation: User: arn:aws:sts::<redacted>:assumed-role/<redacted>/<session-id> is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::<redacted>:role/SubImageScanRole ``` Signed-off-by: Kunaal Sikka <kunaal@subimage.io>
1 parent cc602ea commit 8a79bd4

4 files changed

Lines changed: 113 additions & 1 deletion

File tree

cartography/intel/aws/codebuild.py

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Any
33
from typing import Dict
44
from typing import List
5+
from typing import Set
56

67
import boto3
78
import neo4j
@@ -16,6 +17,21 @@
1617
logger = logging.getLogger(__name__)
1718

1819

20+
def _get_available_codebuild_regions(boto3_session: boto3.Session) -> Set[str]:
21+
"""
22+
Return all known CodeBuild regions across available AWS partitions.
23+
"""
24+
available_regions: Set[str] = set()
25+
for partition in boto3_session.get_available_partitions():
26+
available_regions.update(
27+
boto3_session.get_available_regions(
28+
"codebuild",
29+
partition_name=partition,
30+
),
31+
)
32+
return available_regions
33+
34+
1935
@timeit
2036
@aws_handle_regions
2137
def get_all_codebuild_projects(
@@ -113,7 +129,24 @@ def sync(
113129
update_tag: int,
114130
common_job_parameters: Dict[str, Any],
115131
) -> None:
116-
for region in regions:
132+
available_regions = _get_available_codebuild_regions(boto3_session)
133+
if not available_regions:
134+
logger.warning(
135+
"Could not determine available CodeBuild regions. Continuing with requested regions.",
136+
)
137+
codebuild_regions = regions
138+
else:
139+
codebuild_regions = []
140+
for region in regions:
141+
if region in available_regions:
142+
codebuild_regions.append(region)
143+
else:
144+
logger.info(
145+
"Skipping CodeBuild sync for unsupported region '%s'.",
146+
region,
147+
)
148+
149+
for region in codebuild_regions:
117150
logger.info(
118151
f"Syncing CodeBuild for region '{region}' in account '{current_aws_account_id}'.",
119152
)

cartography/util.py

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@
2727
import boto3
2828
import botocore
2929
import neo4j
30+
from botocore.exceptions import ConnectTimeoutError
3031
from botocore.exceptions import EndpointConnectionError
32+
from botocore.exceptions import ReadTimeoutError
3133
from botocore.parsers import ResponseParserError
3234

3335
from cartography.graph.job import GraphJob
@@ -718,6 +720,12 @@ def inner_function(*args, **kwargs): # type: ignore
718720
"resource is not available in this region. Skipping.",
719721
)
720722
return []
723+
except (ConnectTimeoutError, ReadTimeoutError):
724+
logger.warning(
725+
"Encountered a timeout while calling a regional AWS endpoint. "
726+
"Skipping this region.",
727+
)
728+
return []
721729

722730
return cast(AWSGetFunc, inner_function)
723731

tests/unit/cartography/intel/aws/test_codebuild.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
from unittest.mock import MagicMock
2+
from unittest.mock import patch
23

4+
import pytest
5+
from botocore.exceptions import ClientError
6+
from botocore.exceptions import ConnectTimeoutError
37
from botocore.exceptions import EndpointConnectionError
48

59
from cartography.intel.aws import codebuild
10+
from tests.data.aws.codebuild import GET_PROJECTS
611

712

813
def test_get_all_codebuild_projects_endpoint_connection_error():
@@ -21,3 +26,60 @@ def test_get_all_codebuild_projects_endpoint_connection_error():
2126
# failures are often about service availability in that region, not client/server
2227
# network issues
2328
assert result == []
29+
30+
31+
def test_get_all_codebuild_projects_connect_timeout_error():
32+
"""Ensure ConnectTimeoutError is handled gracefully."""
33+
boto3_session = MagicMock()
34+
paginator = boto3_session.client.return_value.get_paginator.return_value
35+
paginator.paginate.side_effect = ConnectTimeoutError(
36+
endpoint_url="https://codebuild.ca-west-1.amazonaws.com"
37+
)
38+
39+
result = codebuild.get_all_codebuild_projects(boto3_session, "ca-west-1")
40+
41+
assert result == []
42+
43+
44+
def test_get_all_codebuild_projects_invalid_token_error_raises():
45+
"""Ensure non-skippable auth/config errors still surface."""
46+
boto3_session = MagicMock()
47+
paginator = boto3_session.client.return_value.get_paginator.return_value
48+
paginator.paginate.side_effect = ClientError(
49+
{
50+
"Error": {
51+
"Code": "InvalidToken",
52+
"Message": "token invalid",
53+
},
54+
},
55+
"ListProjects",
56+
)
57+
58+
with pytest.raises(RuntimeError):
59+
codebuild.get_all_codebuild_projects(boto3_session, "us-east-1")
60+
61+
62+
@patch.object(codebuild, "cleanup")
63+
@patch.object(codebuild, "load_codebuild_projects")
64+
@patch.object(codebuild, "get_all_codebuild_projects", return_value=GET_PROJECTS)
65+
def test_sync_skips_unsupported_region(
66+
mock_get_all_codebuild_projects,
67+
mock_load_codebuild_projects,
68+
mock_cleanup,
69+
):
70+
boto3_session = MagicMock()
71+
boto3_session.get_available_partitions.return_value = ["aws"]
72+
boto3_session.get_available_regions.return_value = ["us-east-1"]
73+
74+
codebuild.sync(
75+
neo4j_session=MagicMock(),
76+
boto3_session=boto3_session,
77+
regions=["us-east-1", "ca-west-1"],
78+
current_aws_account_id="123456789012",
79+
update_tag=123,
80+
common_job_parameters={"UPDATE_TAG": 123, "AWS_ID": "123456789012"},
81+
)
82+
83+
mock_get_all_codebuild_projects.assert_called_once_with(boto3_session, "us-east-1")
84+
mock_load_codebuild_projects.assert_called_once()
85+
mock_cleanup.assert_called_once()

tests/unit/cartography/test_util.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ def raises_unsupported_error(a, b):
136136
with pytest.raises(ZeroDivisionError):
137137
raises_unsupported_error(1, 2)
138138

139+
# regional connectivity timeouts should be skipped
140+
@aws_handle_regions
141+
def raises_connect_timeout_error(a, b):
142+
raise botocore.exceptions.ConnectTimeoutError(
143+
endpoint_url="https://codebuild.ca-west-1.amazonaws.com",
144+
)
145+
146+
assert raises_connect_timeout_error(1, 2) == []
147+
139148

140149
def test_is_service_control_policy_explicit_deny():
141150
scp_error = botocore.exceptions.ClientError(

0 commit comments

Comments
 (0)