From 5c61048eee54df89e2205eecbdd4aa2d1c0b9892 Mon Sep 17 00:00:00 2001 From: Sven Marnach Date: Wed, 16 Sep 2020 22:22:29 +0200 Subject: [PATCH] Refactor the AWS client to facilitate follow-up requests. --- aws/client.py | 162 +++++++++++++++++++++++--------- aws/ec2/resources.py | 29 +++--- aws/elasticsearch/resources.py | 2 + aws/elb/resources.py | 47 +++------- aws/iam/resources.py | 166 +++++++++++++-------------------- aws/rds/resources.py | 48 ++++------ aws/s3/resources.py | 60 ++++-------- aws/sns/resources.py | 9 +- 8 files changed, 250 insertions(+), 273 deletions(-) diff --git a/aws/client.py b/aws/client.py index a42f08f..260a5e6 100644 --- a/aws/client.py +++ b/aws/client.py @@ -103,63 +103,93 @@ def cache_key(call): ) -def get_aws_resource( +def get_single_region( service_name, method_name, call_args, call_kwargs, cache, - profiles, - regions, + profile, + region, result_from_error=None, debug_calls=False, debug_cache=False, ): """ - Fetches and yields AWS API JSON responses for all profiles and regions (list params) + Fetches AWS API JSON responses for a single profile and region. """ - for profile, region in itertools.product(profiles, regions): - call = default_call._replace( - profile=profile, - region=region, - service=service_name, - method=method_name, - args=call_args, - kwargs=call_kwargs, - ) + call = default_call._replace( + profile=profile, + region=region, + service=service_name, + method=method_name, + args=call_args, + kwargs=call_kwargs, + ) - if debug_calls: - print("calling", call) + if debug_calls: + print("calling", call) - result = None - if cache is not None: - ckey = cache_key(call) - result = cache.get(ckey, None) + result = None + if cache is not None: + ckey = cache_key(call) + result = cache.get(ckey, None) - if debug_cache and result is not None: - print("found cached value for", ckey) + if debug_cache and result is not None: + print("found cached value for", ckey) - if result is None: - client = get_client(call.profile, call.region, call.service) - try: - result = full_results(client, call.method, call.args, call.kwargs) - result["__pytest_meta"] = dict(profile=call.profile, region=call.region) - except botocore.exceptions.ClientError as error: - if result_from_error is None: - raise error - else: - if debug_calls: - print("error fetching resource", error, call) + if result is None: + client = get_client(call.profile, call.region, call.service) + try: + result = full_results(client, call.method, call.args, call.kwargs) + result["__pytest_meta"] = dict(profile=call.profile, region=call.region) + except botocore.exceptions.ClientError as error: + if result_from_error is None: + raise error + else: + if debug_calls: + print("error fetching resource", error, call) - result = result_from_error(error, call) + result = result_from_error(error, call) - if cache is not None: - if debug_cache: - print("setting cache value for", ckey) + if cache is not None: + if debug_cache: + print("setting cache value for", ckey) + + cache.set(ckey, result) + + return result - cache.set(ckey, result) - yield result +def get_aws_resource( + service_name, + method_name, + call_args, + call_kwargs, + cache, + profiles, + regions, + result_from_error=None, + debug_calls=False, + debug_cache=False, +): + """ + Fetches and yields AWS API JSON responses for all profiles and regions (list params) + """ + for profile in profiles: + for region in regions: + yield get_single_region( + service_name, + method_name, + call_args, + call_kwargs, + cache, + profile, + region, + result_from_error=None, + debug_calls=False, + debug_cache=False, + ) class BotocoreClient: @@ -178,8 +208,6 @@ def __init__(self, profiles, regions, cache, debug_calls, debug_cache, offline): else: self.regions = get_available_regions() - self.results = [] - def get_regions(self): if self.offline: return [] @@ -196,7 +224,9 @@ def get( result_from_error=None, do_not_cache=False, ): - + """ + Retrieve resources across the given regions and profiles. + """ # TODO: # For services that don't have the concept of regions, # we don't want to do the exact same test N times where @@ -206,9 +236,9 @@ def get( regions = ["us-east-1"] if self.offline: - self.results = [] + results = [] else: - self.results = list( + results = list( get_aws_resource( service_name, method_name, @@ -223,7 +253,49 @@ def get( ) ) - return self + return BotocoreClientResult(results) + + def get_details( + self, + resource, + service_name, + method_name, + call_args, + call_kwargs, + result_from_error=None, + do_not_cache=False, + ): + """ + Retrieve details for the given resource. + + The resource should be a dictionary returned by the `get()` method, + containing the `__pytest_meta` dictionary with metadata. You still need + to pass whatever argument the botocore function requires to identify + the resource you are requesting details of. + """ + if self.offline: + return None + return get_single_region( + service_name, + method_name, + call_args, + call_kwargs, + profile=resource["__pytest_meta"]["profile"], + region=resource["__pytest_meta"]["region"], + cache=self.cache if not do_not_cache else None, + result_from_error=result_from_error, + debug_calls=self.debug_calls, + debug_cache=self.debug_cache, + ) + + +class BotocoreClientResult: + """ + A result list returned by BotocoreClient.get(). + """ + + def __init__(self, results): + self.results = results def values(self): """Returns the wrapped value @@ -317,7 +389,7 @@ def flatten(self): ... TypeError: can only concatenate list (not "dict") to list """ - self.results = sum(self.results, []) + self.results = list(itertools.chain.from_iterable(self.results)) return self def debug(self): diff --git a/aws/ec2/resources.py b/aws/ec2/resources.py index 83e9ab6..0b625a6 100644 --- a/aws/ec2/resources.py +++ b/aws/ec2/resources.py @@ -64,22 +64,19 @@ def ec2_ebs_snapshots(): def ec2_ebs_snapshots_create_permission(): "https://botocore.amazonaws.com/v1/documentation/api/latest/reference/services/ec2.html#EC2.Client.describe_snapshot_attribute" - return sum( - [ - botocore_client.get( - service_name="ec2", - method_name="describe_snapshot_attribute", - call_args=[], - call_kwargs={ - "Attribute": "createVolumePermission", - "SnapshotId": snapshot["SnapshotId"], - }, - regions=[snapshot["__pytest_meta"]["region"]], - ).values() - for snapshot in ec2_ebs_snapshots() - ], - [], - ) + return [ + botocore_client.get_details( + resource=snapshot, + service_name="ec2", + method_name="describe_snapshot_attribute", + call_args=[], + call_kwargs={ + "Attribute": "createVolumePermission", + "SnapshotId": snapshot["SnapshotId"], + }, + ) + for snapshot in ec2_ebs_snapshots() + ] def ec2_flow_logs(): diff --git a/aws/elasticsearch/resources.py b/aws/elasticsearch/resources.py index ab5c304..ea73497 100644 --- a/aws/elasticsearch/resources.py +++ b/aws/elasticsearch/resources.py @@ -4,6 +4,8 @@ def elasticsearch_domains(): """ http://botocore.readthedocs.io/en/latest/reference/services/es.html#ElasticsearchService.Client.describe_elasticsearch_domains + + This test does not work properly when using multiple AWS accounts. """ # You can only get 5 at a time. domains_list = list_elasticsearch_domains() diff --git a/aws/elb/resources.py b/aws/elb/resources.py index 5b37da2..cce7479 100644 --- a/aws/elb/resources.py +++ b/aws/elb/resources.py @@ -12,31 +12,19 @@ def elbs(with_tags=True): .values() ) - if not with_tags: - return elbs - - elbs_with_tags = [] - for elb in elbs: - tags = ( - botocore_client.get( + if with_tags: + for elb in elbs: + tags = botocore_client.get_details( + resource=elb, service_name="elb", method_name="describe_tags", call_args=[], call_kwargs={"LoadBalancerNames": [elb["LoadBalancerName"]]}, - regions=[elb["__pytest_meta"]["region"]], - ) - .extract_key("TagDescriptions") - .flatten() - .values() - ) - # This check is probably unneeded - if len(tags) >= 1: - tags = tags[0] - if "Tags" in tags: - elb["Tags"] = tags["Tags"] - elbs_with_tags.append(elb) + )["TagDescriptions"] + if "Tags" in tags: + elb["Tags"] = tags["Tags"] - return elbs_with_tags + return elbs def elbs_v2(): @@ -55,18 +43,13 @@ def elb_attributes(elb): """ https://botocore.amazonaws.com/v1/documentation/api/latest/reference/services/elb.html#ElasticLoadBalancing.Client.describe_load_balancer_attributes """ - return ( - botocore_client.get( - "elb", - "describe_load_balancer_attributes", - [], - call_kwargs={"LoadBalancerName": elb["LoadBalancerName"]}, - regions=[elb["__pytest_meta"]["region"]], - ) - .extract_key("LoadBalancerAttributes") - .debug() - .values() - )[0] + return botocore_client.get_details( + elb, + "elb", + "describe_load_balancer_attributes", + [], + call_kwargs={"LoadBalancerName": elb["LoadBalancerName"]}, + )["LoadBalancerAttributes"] def elbs_with_attributes(): diff --git a/aws/iam/resources.py b/aws/iam/resources.py index d6ad7f6..46cd917 100644 --- a/aws/iam/resources.py +++ b/aws/iam/resources.py @@ -1,4 +1,5 @@ import csv +import itertools import time import pytest @@ -22,65 +23,52 @@ def iam_admin_users(): ] -def iam_inline_policies(username): +def iam_inline_policies(user): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_user_policies" - return ( - botocore_client.get("iam", "list_user_policies", [], {"UserName": username}) - .extract_key("PolicyNames") - .flatten() - .values() - ) + return botocore_client.get_details( + user, "iam", "list_user_policies", [], {"UserName": user["UserName"]} + )["PolicyNames"] -def iam_managed_policies(username): +def iam_managed_policies(user): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_attached_user_policies" - return ( - botocore_client.get( - "iam", "list_attached_user_policies", [], {"UserName": username} - ) - .extract_key("AttachedPolicies") - .flatten() - .values() - ) + return botocore_client.get_details( + user, "iam", "list_attached_user_policies", [], {"UserName": user["UserName"]} + )["AttachedPolicies"] -def iam_user_groups(username): +def iam_user_groups(user): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_groups_for_user" - return ( - botocore_client.get("iam", "list_groups_for_user", [], {"UserName": username}) - .extract_key("Groups") - .flatten() - .values() - ) + return botocore_client.get_details( + user, "iam", "list_groups_for_user", [], {"UserName": user["UserName"]} + )["Groups"] -def iam_user_group_inline_policies(username): +def iam_user_group_inline_policies(user): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_group_policies" return [ - botocore_client.get( - "iam", "list_group_policies", [], {"GroupName": group["GroupName"]} - ) - .extract_key("PolicyNames") - .flatten() - .values() - for group in iam_user_groups(username) + botocore_client.get_details( + group, "iam", "list_group_policies", [], {"GroupName": group["GroupName"]} + )["PolicyNames"] + for group in iam_user_groups(user) ] -def iam_user_group_managed_policies(username): +def iam_user_group_managed_policies(user): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_attached_group_policies" return [ - botocore_client.get( - "iam", "list_attached_group_policies", [], {"GroupName": group["GroupName"]} - ) - .extract_key("AttachedPolicies") - .flatten() - .values() - for group in iam_user_groups(username) + botocore_client.get_details( + group, + "iam", + "list_attached_group_policies", + [], + {"GroupName": group["GroupName"]}, + )["AttachedPolicies"] + for group in iam_user_groups(user) ] -def iam_all_user_policies(username): +def iam_all_user_policies(user): """ Gets all policies that can be attached to a user. This includes: - Inline policies on the user @@ -92,19 +80,16 @@ def iam_all_user_policies(username): allow for standard access to the policy name ({'PolicyName': policy_name}) """ inline = [] - inline_policies = [ - iam_inline_policies(username=username) - + iam_user_group_inline_policies(username=username) - ] + inline_policies = [iam_inline_policies(user) + iam_user_group_inline_policies(user)] for policies in inline_policies: for policy_name in policies: if isinstance(policy_name, str): - inline += {"PolicyName": policy_name} + inline.append({"PolicyName": policy_name}) managed = [ policy - for policies in iam_managed_policies(username=username) - + iam_user_group_managed_policies(username=username) + for policies in iam_managed_policies(user) + + iam_user_group_managed_policies(user) for policy in policies ] @@ -112,17 +97,13 @@ def iam_all_user_policies(username): def iam_users_with_policies(): - return [ - {**{"Policies": iam_all_user_policies(username=user["UserName"])}, **user} - for user in iam_users() - ] + return [{"Policies": iam_all_user_policies(user), **user} for user in iam_users()] def iam_users_with_policies_and_groups(): """Users with their associated Policies and Groups""" return [ - {**{"Groups": iam_user_groups(username=user["UserName"])}, **user} - for user in iam_users_with_policies() + {"Groups": iam_user_groups(user), **user} for user in iam_users_with_policies() ] @@ -138,26 +119,25 @@ def iam_admin_mfa_devices(): def iam_user_login_profiles(): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.get_login_profile" - return iam_login_profiles([user for user in iam_users()]) + return iam_login_profiles(user for user in iam_users()) def iam_user_mfa_devices(): "botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_mfa_devices" - return iam_mfa_devices([user for user in iam_users()]) + return iam_mfa_devices(user for user in iam_users()) def iam_login_profiles(users): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.get_login_profile" return [ - botocore_client.get( + botocore_client.get_details( + user, "iam", "get_login_profile", [], {"UserName": user["UserName"]}, result_from_error=lambda error, call: {"LoginProfile": None}, - ) - .extract_key("LoginProfile") - .values()[0] + )["LoginProfile"] for user in users ] @@ -165,11 +145,9 @@ def iam_login_profiles(users): def iam_mfa_devices(users): "botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_mfa_devices" return [ - botocore_client.get( - "iam", "list_mfa_devices", [], {"UserName": user["UserName"]} - ) - .extract_key("MFADevices") - .values()[0] + botocore_client.get_details( + user, "iam", "list_mfa_devices", [], {"UserName": user["UserName"]} + )["MFADevices"] for user in users ] @@ -184,60 +162,44 @@ def iam_roles(): ) -def iam_all_role_policies(rolename): +def iam_all_role_policies(role): return [ - {"PolicyName": policy_name} - for policy_name in iam_role_inline_policies(rolename=rolename) - ] + iam_role_managed_policies(rolename=rolename) + {"PolicyName": policy_name} for policy_name in iam_role_inline_policies(role) + ] + iam_role_managed_policies(role) def iam_roles_with_policies(): - return [ - {**{"Policies": iam_all_role_policies(rolename=role["RoleName"])}, **role} - for role in iam_roles() - ] + return [{"Policies": iam_all_role_policies(role), **role} for role in iam_roles()] -def iam_role_inline_policies(rolename): +def iam_role_inline_policies(role): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_role_policies" - return ( - botocore_client.get("iam", "list_role_policies", [], {"RoleName": rolename}) - .extract_key("PolicyNames") - .flatten() - .values() - ) + return botocore_client.get_details( + role, "iam", "list_role_policies", [], {"RoleName": role["RoleName"]} + )["PolicyNames"] -def iam_role_managed_policies(rolename): +def iam_role_managed_policies(role): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_attached_role_policies" - return ( - botocore_client.get( - "iam", "list_attached_role_policies", [], {"RoleName": rolename} - ) - .extract_key("AttachedPolicies") - .flatten() - .values() - ) + return botocore_client.get_details( + role, "iam", "list_attached_role_policies", [], {"RoleName": role["RoleName"]} + )["AttachedPolicies"] def iam_admin_roles(): return [role for role in iam_roles_with_policies() if user_is_admin(role)] -def iam_access_keys_for_user(username): +def iam_access_keys_for_user(user): "https://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.list_access_keys" - return ( - botocore_client.get("iam", "list_access_keys", [], {"UserName": username}) - .extract_key("AccessKeyMetadata") - .flatten() - .values() - ) + return botocore_client.get_details( + user, "iam", "list_access_keys", [], {"UserName": user["UserName"]} + )["AccessKeyMetadata"] def iam_get_all_access_keys(): - return sum( - [iam_access_keys_for_user(username=user["UserName"]) for user in iam_users()], - [], + return itertools.chain.from_iterable( + iam_access_keys_for_user(user) for user in iam_users() ) @@ -245,8 +207,8 @@ def iam_generate_credential_report(): "http://botocore.readthedocs.io/en/latest/reference/services/iam.html#IAM.Client.generate_credential_report" results = botocore_client.get( "iam", "generate_credential_report", [], {}, do_not_cache=True - ).results - if len(results): + ).values() + if results: return results[0].get("State") return "" @@ -263,8 +225,8 @@ def iam_get_credential_report(): # We want this to blow up if it can't get the "Content" results = botocore_client.get( "iam", "get_credential_report", [], {}, do_not_cache=True - ).results - if not len(results): + ).values() + if not results: return [] content = results[0]["Content"] decoded_content = content.decode("utf-8") diff --git a/aws/rds/resources.py b/aws/rds/resources.py index 08d259a..51aba29 100644 --- a/aws/rds/resources.py +++ b/aws/rds/resources.py @@ -1,3 +1,5 @@ +import itertools + from conftest import botocore_client @@ -13,20 +15,14 @@ def rds_db_instances(): def rds_db_instance_tags(db): "http://botocore.readthedocs.io/en/latest/reference/services/rds.html#RDS.Client.list_tags_for_resource" - return ( - botocore_client.get( - service_name="rds", - method_name="list_tags_for_resource", - call_args=[], - call_kwargs={"ResourceName": db["DBInstanceArn"]}, - profiles=[db["__pytest_meta"]["profile"]], - regions=[db["__pytest_meta"]["region"]], - result_from_error=lambda e, call: [], - ) - .extract_key("TagList") - .flatten() - .values() - ) + return botocore_client.get_details( + resource=db, + service_name="rds", + method_name="list_tags_for_resource", + call_args=[], + call_kwargs={"ResourceName": db["DBInstanceArn"]}, + result_from_error=lambda e, call: [], + )["TagList"] def rds_db_instances_with_tags(): @@ -37,8 +33,9 @@ def rds_db_instances_with_tags(): def rds_db_instances_vpc_security_groups(): "http://botocore.readthedocs.io/en/latest/reference/services/ec2.html#EC2.Client.describe_security_groups" - return [ - botocore_client.get( + return itertools.chain.from_iterable( + botocore_client.get_details( + resource=instance, service_name="ec2", method_name="describe_security_groups", call_args=[], @@ -54,15 +51,10 @@ def rds_db_instances_vpc_security_groups(): } ] }, - profiles=[instance["__pytest_meta"]["profile"]], - regions=[instance["__pytest_meta"]["region"]], result_from_error=lambda e, call: {"SecurityGroups": []}, - ) # treat not found as empty list - .extract_key("SecurityGroups") - .flatten() - .values() + )["SecurityGroups"] for instance in rds_db_instances() - ] + ) def rds_db_snapshots(): @@ -79,18 +71,14 @@ def rds_db_snapshots_attributes(): "http://botocore.readthedocs.io/en/latest/reference/services/rds.html#RDS.Client.describe_db_snapshot_attributes" empty_attrs = {"DBSnapshotAttributesResult": {"DBSnapshotAttributes": []}} return [ - botocore_client.get( + botocore_client.get_details( + resource="snapshot", service_name="rds", method_name="describe_db_snapshot_attributes", call_args=[], call_kwargs={"DBSnapshotIdentifier": snapshot["DBSnapshotIdentifier"]}, - profiles=[snapshot["__pytest_meta"]["profile"]], - regions=[snapshot["__pytest_meta"]["region"]], result_from_error=lambda e, call: empty_attrs, # treat not found as empty list - ) - .extract_key("DBSnapshotAttributesResult") - .extract_key("DBSnapshotAttributes") - .values()[0] + )["DBSnapshotAttributesResult"]["DBSnapshotAttributes"] for snapshot in rds_db_snapshots() ] diff --git a/aws/s3/resources.py b/aws/s3/resources.py index cbae993..3fa6167 100644 --- a/aws/s3/resources.py +++ b/aws/s3/resources.py @@ -14,17 +14,14 @@ def s3_buckets(): def s3_buckets_cors_rules(): "http://botocore.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.get_bucket_cors" return [ - botocore_client.get( + botocore_client.get_details( + bucket, "s3", "get_bucket_cors", [], {"Bucket": bucket["Name"]}, - profiles=[bucket["__pytest_meta"]["profile"]], - regions=[bucket["__pytest_meta"]["region"]], result_from_error=lambda error, call: {"CORSRules": None}, - ) - .extract_key("CORSRules") - .values()[0] + )["CORSRules"] for bucket in s3_buckets() ] @@ -32,16 +29,9 @@ def s3_buckets_cors_rules(): def s3_buckets_logging(): "http://botocore.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.get_bucket_logging" return [ - botocore_client.get( - "s3", - "get_bucket_logging", - [], - {"Bucket": bucket["Name"]}, - profiles=[bucket["__pytest_meta"]["profile"]], - regions=[bucket["__pytest_meta"]["region"]], - ) - .extract_key("LoggingEnabled", default=False) - .values()[0] + botocore_client.get_details( + bucket, "s3", "get_bucket_logging", [], {"Bucket": bucket["Name"]}, + ).get("LoggingEnabled", False) for bucket in s3_buckets() ] @@ -49,14 +39,9 @@ def s3_buckets_logging(): def s3_buckets_acls(): "http://botocore.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.get_bucket_acl" return [ - botocore_client.get( - "s3", - "get_bucket_acl", - [], - {"Bucket": bucket["Name"]}, - profiles=[bucket["__pytest_meta"]["profile"]], - regions=[bucket["__pytest_meta"]["region"]], - ).values()[0] + botocore_client.get_details( + bucket, "s3", "get_bucket_acl", [], {"Bucket": bucket["Name"]}, + ) for bucket in s3_buckets() ] @@ -64,14 +49,9 @@ def s3_buckets_acls(): def s3_buckets_versioning(): "http://botocore.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.get_bucket_versioning" return [ - botocore_client.get( - "s3", - "get_bucket_versioning", - [], - {"Bucket": bucket["Name"]}, - profiles=[bucket["__pytest_meta"]["profile"]], - regions=[bucket["__pytest_meta"]["region"]], - ).values()[0] + botocore_client.get_details( + bucket, "s3", "get_bucket_versioning", [], {"Bucket": bucket["Name"]}, + ) for bucket in s3_buckets() ] @@ -86,31 +66,27 @@ def s3_buckets_website(): return [ website for bucket in s3_buckets() - for website in botocore_client.get( + for website in botocore_client.get_details( + bucket, "s3", "get_bucket_website", [], {"Bucket": bucket["Name"]}, - profiles=[bucket["__pytest_meta"]["profile"]], - regions=[bucket["__pytest_meta"]["region"]], result_from_error=lambda e, call: empty_response, - ).values() + ) ] def s3_buckets_policy(): "http://botocore.readthedocs.io/en/latest/reference/services/s3.html#S3.Client.get_bucket_policy" return [ - botocore_client.get( + botocore_client.get_details( + bucket, "s3", "get_bucket_policy", [], {"Bucket": bucket["Name"]}, - profiles=[bucket["__pytest_meta"]["profile"]], - regions=[bucket["__pytest_meta"]["region"]], result_from_error=lambda e, call: {"Policy": ""}, - ) - .extract_key("Policy") - .values()[0] + )["Policy"] for bucket in s3_buckets() ] diff --git a/aws/sns/resources.py b/aws/sns/resources.py index 0461ae5..a0d3320 100644 --- a/aws/sns/resources.py +++ b/aws/sns/resources.py @@ -14,15 +14,12 @@ def sns_subscriptions(): def sns_subscription_attributes(): "https://boto3.amazonaws.com/v1/documentation/api/latest/reference/services/sns.html#subscription" return [ - botocore_client.get( + botocore_client.get_details( + resource=subscription, service_name="sns", method_name="get_subscription_attributes", call_args=[], call_kwargs={"SubscriptionArn": subscription["SubscriptionArn"]}, - profiles=[subscription["__pytest_meta"]["profile"]], - regions=[subscription["__pytest_meta"]["region"]], - ) - .extract_key("Attributes") - .values()[0] + )["Attributes"] for subscription in sns_subscriptions() ]