Spaces:
Runtime error
Runtime error
from typing import Optional, Any, List, Dict | |
from phi.aws.api_client import AwsApiClient | |
from phi.aws.resource.base import AwsResource | |
from phi.aws.resource.iam.policy import IamPolicy | |
from phi.cli.console import print_info | |
from phi.utils.log import logger | |
class IamRole(AwsResource): | |
""" | |
Reference: | |
- https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/iam.html#service-resource | |
""" | |
resource_type: Optional[str] = "IamRole" | |
service_name: str = "iam" | |
# RoleName: The name of the role to create. | |
name: str | |
# The trust relationship policy document that grants an entity permission to assume the role. | |
assume_role_policy_document: str | |
# The path to the role. This parameter is optional. If it is not included, it defaults to a slash (/). | |
path: Optional[str] = None | |
# A description of the role. | |
description: Optional[str] = None | |
# The maximum session duration (in seconds) that you want to set for the specified role. | |
# If you do not specify a value for this setting, the default maximum of one hour is applied. | |
# This setting can have a value from 1 hour to 12 hours. | |
max_session_duration: Optional[int] = None | |
# The ARN of the policy that is used to set the permissions boundary for the role. | |
permissions_boundary: Optional[str] = None | |
# A list of tags that you want to attach to the new role. Each tag consists of a key name and an associated value. | |
tags: Optional[List[Dict[str, str]]] = None | |
# List of IAM policies to | |
# attach to the role after it is created | |
policies: Optional[List[IamPolicy]] = None | |
# List of IAM policy ARNs (Amazon Resource Name) to | |
# attach to the role after it is created | |
policy_arns: Optional[List[str]] = None | |
# The Amazon Resource Name (ARN) specifying the role. | |
# To get the arn, use get_arn() function | |
arn: Optional[str] = None | |
def _create(self, aws_client: AwsApiClient) -> bool: | |
"""Creates the IamRole | |
Args: | |
aws_client: The AwsApiClient for the current cluster | |
""" | |
print_info(f"Creating {self.get_resource_type()}: {self.get_resource_name()}") | |
try: | |
# create a dict of args which are not null, otherwise aws type validation fails | |
not_null_args: Dict[str, Any] = {} | |
if self.path: | |
not_null_args["Path"] = self.path | |
if self.description: | |
not_null_args["Description"] = self.description | |
if self.max_session_duration: | |
not_null_args["MaxSessionDuration"] = self.max_session_duration | |
if self.permissions_boundary: | |
not_null_args["PermissionsBoundary"] = self.permissions_boundary | |
if self.tags: | |
not_null_args["Tags"] = self.tags | |
# Create Role | |
service_resource = self.get_service_resource(aws_client) | |
role = service_resource.create_role( | |
RoleName=self.name, | |
AssumeRolePolicyDocument=self.assume_role_policy_document, | |
**not_null_args, | |
) | |
# logger.debug(f"Role: {role}") | |
# Validate Role creation | |
create_date = role.create_date | |
self.arn = role.arn | |
logger.debug(f"create_date: {create_date}") | |
logger.debug(f"arn: {self.arn}") | |
if create_date is not None: | |
print_info(f"Role created: {self.name}") | |
self.active_resource = role | |
return True | |
logger.error("Role could not be created") | |
except Exception as e: | |
logger.error(f"{self.get_resource_type()} could not be created.") | |
logger.error(e) | |
return False | |
def post_create(self, aws_client: AwsApiClient) -> bool: | |
# Wait for Role to be created | |
if self.wait_for_create: | |
try: | |
print_info(f"Waiting for {self.get_resource_type()} to be created.") | |
waiter = self.get_service_client(aws_client).get_waiter("role_exists") | |
waiter.wait( | |
RoleName=self.name, | |
WaiterConfig={ | |
"Delay": self.waiter_delay, | |
"MaxAttempts": self.waiter_max_attempts, | |
}, | |
) | |
except Exception as e: | |
logger.error("Waiter failed.") | |
logger.error(e) | |
# Attach policy arns to role | |
attach_policy_success = True | |
if self.active_resource is not None and self.policy_arns is not None: | |
_success = self.attach_policy_arns(aws_client) | |
if not _success: | |
attach_policy_success = False | |
# Attach policies to role | |
if self.active_resource is not None and self.policies is not None: | |
_success = self.attach_policies(aws_client) | |
if not _success: | |
attach_policy_success = False | |
# logger.info(f"attach_policy_success: {attach_policy_success}") | |
return attach_policy_success | |
def _read(self, aws_client: AwsApiClient) -> Optional[Any]: | |
"""Returns the IamRole | |
Args: | |
aws_client: The AwsApiClient for the current cluster | |
""" | |
from botocore.exceptions import ClientError | |
logger.debug(f"Reading {self.get_resource_type()}: {self.get_resource_name()}") | |
try: | |
service_resource = self.get_service_resource(aws_client) | |
role = service_resource.Role(name=self.name) | |
role.load() | |
create_date = role.create_date | |
self.arn = role.arn | |
logger.debug(f"create_date: {create_date}") | |
logger.debug(f"arn: {self.arn}") | |
if create_date is not None: | |
logger.debug(f"Role found: {role.role_name}") | |
self.active_resource = role | |
except ClientError as ce: | |
logger.debug(f"ClientError: {ce}") | |
except Exception as e: | |
logger.error(f"Error reading {self.get_resource_type()}.") | |
logger.error(e) | |
return self.active_resource | |
def _delete(self, aws_client: AwsApiClient) -> bool: | |
"""Deletes the IamRole | |
Args: | |
aws_client: The AwsApiClient for the current cluster | |
""" | |
print_info(f"Deleting {self.get_resource_type()}: {self.get_resource_name()}") | |
try: | |
role = self._read(aws_client) | |
# logger.debug(f"Role: {role}") | |
# logger.debug(f"Role type: {type(role)}") | |
self.active_resource = None | |
if role is None: | |
logger.warning(f"No {self.get_resource_type()} to delete") | |
return True | |
# detach all policies | |
policies = role.attached_policies.all() | |
for policy in policies: | |
print_info(f"Detaching policy: {policy}") | |
role.detach_policy(PolicyArn=policy.arn) | |
# detach all instance profiles | |
profiles = role.instance_profiles.all() | |
for profile in profiles: | |
print_info(f"Removing role from profile: {profile}") | |
profile.remove_role(RoleName=role.name) | |
# delete role | |
role.delete() | |
return True | |
except Exception as e: | |
logger.error(f"{self.get_resource_type()} could not be deleted.") | |
logger.error("Please try again or delete resources manually.") | |
logger.error(e) | |
return False | |
def attach_policy_arns(self, aws_client: AwsApiClient) -> bool: | |
""" | |
Attaches the specified managed policy to the specified IAM role. | |
When you attach a managed policy to a role, the managed policy becomes part of the | |
role's permission (access) policy. | |
Returns: | |
True if operation was successful | |
""" | |
if self.policy_arns is None: | |
return True | |
role = self._read(aws_client) | |
if role is None: | |
logger.warning(f"No {self.get_resource_type()} to attach") | |
return True | |
try: | |
# logger.debug("Attaching managed policies to role") | |
for arn in self.policy_arns: | |
if isinstance(arn, str): | |
role.attach_policy(PolicyArn=arn) | |
print_info(f"Attaching policy to {role.role_name}: {arn}") | |
return True | |
except Exception as e: | |
logger.error(e) | |
return False | |
def attach_policies(self, aws_client: AwsApiClient) -> bool: | |
""" | |
Returns: | |
True if operation was successful | |
""" | |
if self.policies is None: | |
return True | |
role = self._read(aws_client) | |
if role is None: | |
logger.warning(f"No {self.get_resource_type()} to attach") | |
return True | |
try: | |
logger.debug("Attaching managed policies to role") | |
for policy in self.policies: | |
if policy.arn is None: | |
create_success = policy.create(aws_client) | |
if not create_success: | |
return False | |
if policy.arn is not None: | |
role.attach_policy(PolicyArn=policy.arn) | |
print_info(f"Attaching policy to {role.role_name}: {policy.arn}") | |
return True | |
except Exception as e: | |
logger.error(e) | |
return False | |
def get_arn(self, aws_client: AwsApiClient) -> Optional[str]: | |
role = self._read(aws_client) | |
if role is None: | |
return None | |
self.arn = role.arn | |
return self.arn | |