Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

308 lines
7.7 KiB
Python

"""IAM role creation with policies."""
import json
import time
from contextlib import suppress
from typing import Any
from botocore.exceptions import ClientError
from tests.shared.infrastructure_sdk.deployer import (
DEFAULT_REGION,
get_boto3_client,
get_standard_tags,
)
# Common trust policies
LAMBDA_TRUST_POLICY = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "lambda.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
ECS_TASK_TRUST_POLICY = {
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {"Service": "ecs-tasks.amazonaws.com"},
"Action": "sts:AssumeRole",
}
],
}
# Common managed policy ARNs
LAMBDA_BASIC_EXECUTION_POLICY = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
ECS_TASK_EXECUTION_POLICY = "arn:aws:iam::aws:policy/service-role/AmazonECSTaskExecutionRolePolicy"
def create_lambda_execution_role(
name: str, stack_name: str, region: str = DEFAULT_REGION
) -> dict[str, Any]:
"""Create basic Lambda execution role with CloudWatch logs policy.
Args:
name: Role name.
stack_name: Stack name for tagging.
region: AWS region.
Returns:
Dictionary with role info: arn, name.
"""
iam_client = get_boto3_client("iam", region)
role = _create_role(
client=iam_client,
name=name,
trust_policy=LAMBDA_TRUST_POLICY,
stack_name=stack_name,
description="Lambda execution role",
)
# Attach basic execution policy
attach_policy(name, LAMBDA_BASIC_EXECUTION_POLICY, region)
return role
def create_ecs_task_role(
name: str, stack_name: str, region: str = DEFAULT_REGION
) -> dict[str, Any]:
"""Create ECS task role.
Args:
name: Role name.
stack_name: Stack name for tagging.
region: AWS region.
Returns:
Dictionary with role info: arn, name.
"""
iam_client = get_boto3_client("iam", region)
return _create_role(
client=iam_client,
name=name,
trust_policy=ECS_TASK_TRUST_POLICY,
stack_name=stack_name,
description="ECS task role",
)
def create_ecs_execution_role(
name: str, stack_name: str, region: str = DEFAULT_REGION
) -> dict[str, Any]:
"""Create ECS execution role with ECR and logs permissions.
Args:
name: Role name.
stack_name: Stack name for tagging.
region: AWS region.
Returns:
Dictionary with role info: arn, name.
"""
iam_client = get_boto3_client("iam", region)
role = _create_role(
client=iam_client,
name=name,
trust_policy=ECS_TASK_TRUST_POLICY,
stack_name=stack_name,
description="ECS execution role",
)
# Attach execution policy (includes ECR and logs)
attach_policy(name, ECS_TASK_EXECUTION_POLICY, region)
return role
def _create_role(
client: Any,
name: str,
trust_policy: dict[str, Any],
stack_name: str,
description: str,
) -> dict[str, Any]:
"""Create an IAM role."""
tags = get_standard_tags(stack_name)
try:
response = client.create_role(
RoleName=name,
AssumeRolePolicyDocument=json.dumps(trust_policy),
Description=description,
Tags=tags,
)
role_arn = response["Role"]["Arn"]
except ClientError as e:
if e.response["Error"]["Code"] == "EntityAlreadyExists":
# Role exists, get its ARN
response = client.get_role(RoleName=name)
role_arn = response["Role"]["Arn"]
else:
raise
# Wait for role to be usable (IAM is eventually consistent)
time.sleep(5)
return {
"arn": role_arn,
"name": name,
}
def attach_policy(role_name: str, policy_arn: str, region: str = DEFAULT_REGION) -> None:
"""Attach managed policy to role.
Args:
role_name: Role name.
policy_arn: ARN of managed policy.
region: AWS region.
"""
iam_client = get_boto3_client("iam", region)
try:
iam_client.attach_role_policy(
RoleName=role_name,
PolicyArn=policy_arn,
)
except ClientError as e:
if e.response["Error"]["Code"] != "EntityAlreadyExists":
raise
def detach_policy(role_name: str, policy_arn: str, region: str = DEFAULT_REGION) -> None:
"""Detach managed policy from role.
Args:
role_name: Role name.
policy_arn: ARN of managed policy.
region: AWS region.
"""
iam_client = get_boto3_client("iam", region)
try:
iam_client.detach_role_policy(
RoleName=role_name,
PolicyArn=policy_arn,
)
except ClientError as e:
if e.response["Error"]["Code"] != "NoSuchEntity":
raise
def put_role_policy(
role_name: str,
policy_name: str,
policy_document: dict[str, Any],
region: str = DEFAULT_REGION,
) -> None:
"""Add inline policy to role.
Args:
role_name: Role name.
policy_name: Name for the inline policy.
policy_document: Policy document as dict.
region: AWS region.
"""
iam_client = get_boto3_client("iam", region)
iam_client.put_role_policy(
RoleName=role_name,
PolicyName=policy_name,
PolicyDocument=json.dumps(policy_document),
)
def delete_role_policy(role_name: str, policy_name: str, region: str = DEFAULT_REGION) -> None:
"""Delete inline policy from role.
Args:
role_name: Role name.
policy_name: Policy name.
region: AWS region.
"""
iam_client = get_boto3_client("iam", region)
try:
iam_client.delete_role_policy(
RoleName=role_name,
PolicyName=policy_name,
)
except ClientError as e:
if e.response["Error"]["Code"] != "NoSuchEntity":
raise
def delete_role(name: str, region: str = DEFAULT_REGION) -> None:
"""Delete role and all attached policies.
Args:
name: Role name.
region: AWS region.
"""
iam_client = get_boto3_client("iam", region)
# Detach managed policies
with suppress(ClientError):
attached = iam_client.list_attached_role_policies(RoleName=name)
for policy in attached.get("AttachedPolicies", []):
iam_client.detach_role_policy(RoleName=name, PolicyArn=policy["PolicyArn"])
# Delete inline policies
with suppress(ClientError):
inline = iam_client.list_role_policies(RoleName=name)
for policy_name in inline.get("PolicyNames", []):
iam_client.delete_role_policy(RoleName=name, PolicyName=policy_name)
# Delete role
try:
iam_client.delete_role(RoleName=name)
except ClientError as e:
if e.response["Error"]["Code"] != "NoSuchEntity":
raise
def get_role(name: str, region: str = DEFAULT_REGION) -> dict[str, Any] | None:
"""Get role details.
Args:
name: Role name.
region: AWS region.
Returns:
Role details or None if not found.
"""
iam_client = get_boto3_client("iam", region)
try:
response = iam_client.get_role(RoleName=name)
return {
"arn": response["Role"]["Arn"],
"name": response["Role"]["RoleName"],
}
except ClientError as e:
if e.response["Error"]["Code"] == "NoSuchEntity":
return None
raise
def get_account_id(region: str = DEFAULT_REGION) -> str:
"""Get current AWS account ID.
Args:
region: AWS region.
Returns:
Account ID string.
"""
sts_client = get_boto3_client("sts", region)
return str(sts_client.get_caller_identity()["Account"])