diff --git a/aws_quota/check/__init__.py b/aws_quota/check/__init__.py index be4da52..ccecaa4 100644 --- a/aws_quota/check/__init__.py +++ b/aws_quota/check/__init__.py @@ -4,6 +4,7 @@ from .cloudformation import * from .dynamodb import * from .ebs import * +from .ecr import * from .ec2 import * from .ecs import * from .eks import * diff --git a/aws_quota/check/ec2.py b/aws_quota/check/ec2.py index 5103754..c8cac0a 100644 --- a/aws_quota/check/ec2.py +++ b/aws_quota/check/ec2.py @@ -238,4 +238,30 @@ class LaunchTemplatesCount(QuotaCheck): @property def current(self): - return self.count_paginated_results("ec2", "describe_launch_templates", "LaunchTemplates") \ No newline at end of file + return self.count_paginated_results("ec2", "describe_launch_templates", "LaunchTemplates") + +class AmiCount(QuotaCheck): + key = "ec2_ami_count" + scope = QuotaScope.REGION + service_code = 'ec2' + quota_code = 'L-B665C33B' + description = "The maximum number of public and private AMIs allowed in this Region. These include available, disabled, and pending AMIs, and AMIs in the Recycle Bin." + + @property + def current(self): + return self.count_paginated_results("ec2", "describe_images", "Images", + {"Owners": ["self"], "IncludeDeprecated": True, "IncludeDisabled": True}) + \ + self.count_paginated_results("ec2", "list_images_in_recycle_bin", "Images") + +class PublicAmiCount(QuotaCheck): + key = "ec2_public_ami_count" + scope = QuotaScope.REGION + service_code = 'ec2' + quota_code = 'L-0E3CBAB9' + description = "The maximum number of public AMIs, including public AMIs in the Recycle Bin, allowed in this Region." + + @property + def current(self): + return self.count_paginated_results("ec2", "describe_images", "Images", + {"Owners": ["self"], "IncludeDeprecated": True, "IncludeDisabled": True, + "Filters":[{"Name": "is-public", "Values": ["true"]}]}) diff --git a/aws_quota/check/ecr.py b/aws_quota/check/ecr.py new file mode 100644 index 0000000..941019e --- /dev/null +++ b/aws_quota/check/ecr.py @@ -0,0 +1,43 @@ +from typing import List +from .quota_check import InstanceQuotaCheck, QuotaScope +from aws_quota.utils import get_paginated_results + +import boto3 +import cachetools + + +@cachetools.cached(cache=cachetools.TTLCache(maxsize=1, ttl=60)) +def get_all_repositories(session: boto3.Session) -> List[str]: + services = ['ecr'] + # ECR is available in all regions, but ecr-public is only available in us-east-1 + if session.region_name == 'us-east-1': + services.append('ecr-public') + + return [ + repository['repositoryArn'] + for service in services + for repository in get_paginated_results(session, service, 'describe_repositories', 'repositories') + ] + +@cachetools.cached(cache=cachetools.TTLCache(10000, 60)) # 10k = default number of max registries per account +def get_repository_images(session: boto3.Session, repository_arn: str) -> List[str]: + arn_parts = repository_arn.split(':') + service = arn_parts[2] + repository_name = arn_parts[5].removeprefix("repository/") + return get_paginated_results(session, service, "describe_images", "imageDetails", {'repositoryName': repository_name}) + +class ImagesPerRepository(InstanceQuotaCheck): + key = "ecr_images_per_repository" + scope = QuotaScope.INSTANCE + service_code = 'ecr' + quota_code = 'L-03A36CE1' + description = "The maximum number of images per repository." + instance_id = 'Repository ARN' + + @staticmethod + def get_all_identifiers(session: boto3.Session) -> List[str]: + return get_all_repositories(session) + + @property + def current(self): + return len(get_repository_images(self.boto_session, self.instance_id))