703fe6eb7c
CI / push-validation (pull_request) Successful in 25s
CI / helm (pull_request) Successful in 28s
CI / lint (pull_request) Successful in 45s
CI / build (pull_request) Successful in 45s
CI / quality (pull_request) Successful in 55s
CI / typecheck (pull_request) Successful in 1m0s
CI / security (pull_request) Successful in 1m1s
CI / unit_tests (pull_request) Successful in 4m37s
CI / docker (pull_request) Successful in 1m26s
CI / integration_tests (pull_request) Successful in 8m38s
CI / coverage (pull_request) Successful in 8m41s
CI / status-check (pull_request) Successful in 2s
The generic EC2 fallback iterated over Reservations dicts and called
item.get("InstanceId", "") on each Reservation, which always returned ""
because Reservations have "ReservationId"/"Instances" — not "InstanceId".
Every result had id="" and a malformed ARN.
Add a dedicated handler before the generic fallback that unpacks
response["Reservations"] -> reservation["Instances"] -> InstanceId,
mirroring the pattern used for s3/ecs/eks/iam/lambda/rds.
Also add BDD scenario, step definitions, and mock helper support for
describe_instances returning N instances with non-empty IDs.
ISSUES CLOSED: #1021
147 lines
4.6 KiB
Python
147 lines
4.6 KiB
Python
"""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,
|
|
ec2_instances: 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": []}
|
|
instances = ec2_instances or []
|
|
reservations = (
|
|
[{"ReservationId": "r-00000001", "Instances": instances}]
|
|
if instances
|
|
else []
|
|
)
|
|
client.describe_instances.return_value = {"Reservations": 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
|