Source code for infrahouse_core.aws.iam_instance_profile

"""
IAM Instance Profile resource wrapper.

Provides ``exists`` / ``delete()`` support with dependency-aware teardown
(remove all roles, then delete the instance profile).
"""

from __future__ import annotations

from logging import getLogger
from typing import Optional

from botocore.exceptions import ClientError
from cached_property import cached_property_with_ttl

from infrahouse_core.aws.base import AWSResource
from infrahouse_core.aws.iam_role import IAMRole

LOG = getLogger(__name__)


[docs] class IAMInstanceProfile(AWSResource): """Wrapper around an IAM instance profile. :param profile_name: Name of the IAM instance profile. :param region: AWS region. :param role_arn: IAM role ARN for cross-account access. """ def __init__(self, profile_name, region=None, role_arn=None, session=None): super().__init__(profile_name, "iam", region=region, role_arn=role_arn, session=session) @property def profile_name(self) -> str: """Return the name of the instance profile. :rtype: str """ return self._resource_id @property def exists(self) -> bool: """Return ``True`` if the instance profile exists.""" try: self._client.get_instance_profile(InstanceProfileName=self._resource_id) return True except ClientError as err: if err.response["Error"]["Code"] == "NoSuchEntity": return False raise
[docs] @cached_property_with_ttl(ttl=10) def role(self) -> Optional[IAMRole]: """Return the IAM role associated with this instance profile, or ``None``. An instance profile can have at most one role. :return: The attached :class:`IAMRole`, or ``None`` if no role is attached. :rtype: IAMRole | None """ response = self._client.get_instance_profile(InstanceProfileName=self._resource_id) roles = response["InstanceProfile"]["Roles"] if roles: return IAMRole(roles[0]["RoleName"], region=self._region, role_arn=self._role_arn, session=self._session) return None
[docs] def delete(self) -> None: """Delete the instance profile after removing all roles. Teardown order: 1. Remove all roles from the instance profile. 2. Delete the instance profile itself. Idempotent -- does nothing if the instance profile does not exist. """ try: self.remove_role() self._client.delete_instance_profile(InstanceProfileName=self._resource_id) LOG.info("Deleted IAM instance profile %s", self._resource_id) except ClientError as err: if err.response["Error"]["Code"] == "NoSuchEntity": LOG.info("IAM instance profile %s does not exist.", self._resource_id) else: raise
[docs] def remove_role(self) -> None: """Remove the role from the instance profile, if one is attached.""" role = self.role if role is None: return LOG.info("Removing role %s from instance profile %s", role.role_name, self._resource_id) try: self._client.remove_role_from_instance_profile( InstanceProfileName=self._resource_id, RoleName=role.role_name, ) LOG.debug( "Removed role %s from instance profile %s", role.role_name, self._resource_id, ) except ClientError as err: if err.response["Error"]["Code"] == "NoSuchEntity": LOG.debug("Role already removed from instance profile %s", self._resource_id) else: raise