fix(resource): address PR review findings for AWS SDK CloudResourceHandler
- Extract AWS-specific logic into cloud_aws.py and cloud_providers.py to keep all files under the 500-line limit (cloud.py: 490 lines, cloud_aws.py: 498 lines, cloud_providers.py: 181 lines) - Fix sandbox test regression: change cloud_resources.feature sandbox create scenario from "aws" to "gcp" provider (AWS no longer raises NotImplementedError) - Add ImportError handling to step_sandbox_create/commit/rollback in cloud_resources_steps.py - Remove all 9 type: ignore comments from production code using proper type narrowing and boto3/botocore type stubs in typings/ - Move plan_id validation before logger.info() in all three sandbox methods (fail-fast principle) - Fix exception suppression: discover_aws_resources() now propagates exceptions instead of catching bare Exception - Move deferred imports (PhysVirt, ResourceCapabilities, _derive_child_id) to module level in cloud_aws.py - Split cloud_aws_sdk_steps.py (755 lines) into focused modules: cloud_aws_helpers.py, cloud_aws_session_steps.py, cloud_aws_discover_steps.py, cloud_aws_sandbox_steps.py - Add CHANGELOG.md entry for AWS SDK integration feature - Update robot/helper_cloud_resources.py to handle ImportError/ NotImplementedError for AWS sandbox operations ISSUES CLOSED: #1021
This commit is contained in:
@@ -0,0 +1,139 @@
|
||||
"""Shared helpers for AWS SDK step definitions.
|
||||
|
||||
Provides environment management, resource factories, and mock session
|
||||
builders used across the cloud_aws_* step definition files.
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import contextlib
|
||||
import os
|
||||
from typing import Any
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
from cleveragents.domain.models.core.resource import (
|
||||
PhysVirt,
|
||||
Resource,
|
||||
ResourceCapabilities,
|
||||
)
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Helpers
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_CLOUD_ENV_VARS = [
|
||||
"AWS_ACCESS_KEY_ID",
|
||||
"AWS_SECRET_ACCESS_KEY",
|
||||
"AWS_SESSION_TOKEN",
|
||||
"AWS_REGION",
|
||||
"AWS_PROFILE",
|
||||
"GOOGLE_APPLICATION_CREDENTIALS",
|
||||
"GCLOUD_PROJECT",
|
||||
"GCP_REGION",
|
||||
"AZURE_SUBSCRIPTION_ID",
|
||||
"AZURE_TENANT_ID",
|
||||
"AZURE_CLIENT_ID",
|
||||
"AZURE_CLIENT_SECRET",
|
||||
"AZURE_REGION",
|
||||
]
|
||||
|
||||
|
||||
def _clear_cloud_env() -> dict[str, str]:
|
||||
saved: dict[str, str] = {}
|
||||
for var in _CLOUD_ENV_VARS:
|
||||
val = os.environ.pop(var, None)
|
||||
if val is not None:
|
||||
saved[var] = val
|
||||
return saved
|
||||
|
||||
|
||||
def _restore_cloud_env(saved: dict[str, str]) -> None:
|
||||
for var in _CLOUD_ENV_VARS:
|
||||
if var in saved:
|
||||
os.environ[var] = saved[var]
|
||||
else:
|
||||
os.environ.pop(var, None)
|
||||
|
||||
|
||||
def _make_resource(
|
||||
type_name: str,
|
||||
properties: dict[str, Any] | None = None,
|
||||
location: str | None = None,
|
||||
) -> Resource:
|
||||
return Resource(
|
||||
resource_id="01KJ5C5TPMP8GGX3QC83E2MAQS",
|
||||
resource_type_name=type_name,
|
||||
classification=PhysVirt.PHYSICAL,
|
||||
location=location,
|
||||
properties=properties or {},
|
||||
capabilities=ResourceCapabilities(
|
||||
readable=True,
|
||||
writable=False,
|
||||
sandboxable=False,
|
||||
checkpointable=False,
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def _make_mock_session(
|
||||
account_id: str = "123456789012",
|
||||
sts_error: Exception | None = None,
|
||||
ec2_vpcs: list[dict[str, Any]] | None = None,
|
||||
s3_buckets: list[dict[str, Any]] | None = None,
|
||||
ecs_clusters: list[str] | None = None,
|
||||
client_error: Exception | None = None,
|
||||
) -> MagicMock:
|
||||
"""Build a mock boto3 Session with configurable responses."""
|
||||
session = MagicMock()
|
||||
|
||||
def _make_client(service: str, **_kwargs: Any) -> MagicMock:
|
||||
client = MagicMock()
|
||||
|
||||
if service == "sts":
|
||||
if sts_error is not None:
|
||||
client.get_caller_identity.side_effect = sts_error
|
||||
else:
|
||||
client.get_caller_identity.return_value = {
|
||||
"Account": account_id,
|
||||
"UserId": "AIDAIOSFODNN7EXAMPLE",
|
||||
"Arn": f"arn:aws:iam::{account_id}:user/test",
|
||||
}
|
||||
|
||||
elif service == "ec2":
|
||||
if client_error is not None:
|
||||
client.describe_vpcs.side_effect = client_error
|
||||
client.describe_subnets.side_effect = client_error
|
||||
client.describe_instances.side_effect = client_error
|
||||
client.describe_security_groups.side_effect = client_error
|
||||
else:
|
||||
vpcs = ec2_vpcs or []
|
||||
client.describe_vpcs.return_value = {"Vpcs": vpcs}
|
||||
client.describe_subnets.return_value = {"Subnets": []}
|
||||
client.describe_instances.return_value = {"Reservations": []}
|
||||
client.describe_security_groups.return_value = {"SecurityGroups": []}
|
||||
|
||||
elif service == "s3":
|
||||
if client_error is not None:
|
||||
client.list_buckets.side_effect = client_error
|
||||
else:
|
||||
buckets = s3_buckets or []
|
||||
client.list_buckets.return_value = {"Buckets": buckets}
|
||||
|
||||
elif service == "ecs":
|
||||
if client_error is not None:
|
||||
client.list_clusters.side_effect = client_error
|
||||
else:
|
||||
arns = ecs_clusters or []
|
||||
client.list_clusters.return_value = {"clusterArns": arns}
|
||||
|
||||
else:
|
||||
if client_error is not None:
|
||||
for attr in dir(client):
|
||||
if not attr.startswith("_"):
|
||||
with contextlib.suppress(AttributeError):
|
||||
getattr(client, attr).side_effect = client_error
|
||||
|
||||
return client
|
||||
|
||||
session.client.side_effect = _make_client
|
||||
return session
|
||||
Reference in New Issue
Block a user