diff --git a/.github/workflows/terraform-tests.yml b/.github/workflows/terraform-tests.yml index b091dbd47f36a..cf45b4692cffb 100644 --- a/.github/workflows/terraform-tests.yml +++ b/.github/workflows/terraform-tests.yml @@ -92,6 +92,7 @@ jobs: key: ${{ runner.os }}-go-${{ hashFiles('**/terraform-provider-aws/go.sum') }} - name: Run Test Cases + env: + CI: true run: | - cd terraform-provider-aws - AWS_ACCESS_KEY_ID=test AWS_SECRET_ACCESS_KEY=test TF_ACC=true go test ./internal/service/${{ matrix.service }}/ -v -timeout 60m -run ${{ steps.get-list.outputs.testlist }} + bash tests/terraform/run.sh ${{ matrix.service }} ${{ steps.get-list.outputs.testlist }} diff --git a/localstack/services/route53resolver/models.py b/localstack/services/route53resolver/models.py index 1e1d6f92a65fd..f1bd1466d8bb3 100644 --- a/localstack/services/route53resolver/models.py +++ b/localstack/services/route53resolver/models.py @@ -1,7 +1,12 @@ from typing import Dict -from localstack.aws.api.route53resolver import FirewallRuleGroup, ResourceNotFoundException +from localstack.aws.api.route53resolver import ( + FirewallConfig, + FirewallRuleGroup, + ResourceNotFoundException, +) from localstack.services.generic_proxy import RegionBackend +from localstack.services.route53resolver.utils import get_firewall_config_id, validate_vpc from localstack.utils.aws import aws_stack @@ -15,94 +20,151 @@ def __init__(self): self.firewall_domains = {} self.firewall_rules = {} self.firewall_rule_group_associations = {} - - -## helper functions for the backend -def get_firewall_rule_group(id): - """returns firewall rule group with the given id if it exists""" - region_details = Route53ResolverBackend.get() - firewall_rule_group = region_details.firewall_rule_groups.get(id) - if not firewall_rule_group: - raise ResourceNotFoundException( - f"Can't find the resource with ID '{id}'. Trace Id: '{aws_stack.get_trace_id()}'" + self.resolver_query_log_configs = {} + self.resolver_query_log_config_associations = {} + self.firewall_configs = {} + + ## helper functions for the backend + def get_firewall_rule_group(self, id): + """returns firewall rule group with the given id if it exists""" + + firewall_rule_group = self.firewall_rule_groups.get(id) + if not firewall_rule_group: + raise ResourceNotFoundException( + f"Can't find the resource with ID '{id}'. Trace Id: '{aws_stack.get_trace_id()}'" + ) + return firewall_rule_group + + def delete_firewall_rule_group(self, id): + """deletes the firewall rule group with the given id""" + # if firewall_rule_groups doesn't exist it will throw an error + + firewall_rule_group = self.get_firewall_rule_group(id) + self.firewall_rule_groups.pop(id) + return firewall_rule_group + + def get_firewall_rule_group_association(self, id): + """returns firewall rule group association with the given id if it exists""" + + firewall_rule_group_association = self.firewall_rule_group_associations.get(id) + if not firewall_rule_group_association: + raise ResourceNotFoundException( + f"[RSLVR-02025] Can't find the resource with ID '{id}'. Trace Id: '{aws_stack.get_trace_id()}'" + ) + return self.firewall_rule_group_associations.get(id) + + def delete_firewall_rule_group_association(self, id): + """deletes the firewall rule group association with the given id""" + # if firewall_rule_group_associations doesn't exist it will throw an error + + firewall_rule_group_associations = self.get_firewall_rule_group_association(id) + self.firewall_rule_group_associations.pop(id) + return firewall_rule_group_associations + + def get_firewall_domain(self, d): + """returns firewall domain with the given id if it exists""" + # firewall_domain can return none + + firewall_domain = self.firewall_domains.get(id) + return firewall_domain + + def get_firewall_domain_list(self, id): + """returns firewall domain list with the given id if it exists""" + + firewall_domain_list = self.firewall_domain_lists.get(id) + if not firewall_domain_list: + raise ResourceNotFoundException( + f"Can't find the resource with ID '{id}'. Trace Id: '{aws_stack.get_trace_id()}'" + ) + return firewall_domain_list + + def delete_firewall_domain_list(self, id): + """deletes the firewall domain list with the given id""" + # if firewall_domain_lists doesn't exist it will throw an error + + firewall_domain_list = self.get_firewall_domain_list(id) + self.firewall_domain_lists.pop(id) + return firewall_domain_list + + def get_firewall_rule(self, firewall_rule_group_id, firewall_domain_list_id): + """returns firewall rule with the given id if it exists""" + + firewall_rule = self.firewall_rules.get(firewall_rule_group_id, {}).get( + firewall_domain_list_id ) - return firewall_rule_group - - -def delete_firewall_rule_group(id): - """deletes the firewall rule group with the given id""" - # if firewall_rule_groups doesn't exist it will throw an error - region_details = Route53ResolverBackend.get() - firewall_rule_group = get_firewall_rule_group(id) - region_details.firewall_rule_groups.pop(id) - return firewall_rule_group - - -def get_firewall_rule_group_association(id): - """returns firewall rule group association with the given id if it exists""" - region_details = Route53ResolverBackend.get() - firewall_rule_group_association = region_details.firewall_rule_group_associations.get(id) - if not firewall_rule_group_association: - raise ResourceNotFoundException( - f"[RSLVR-02025] Can't find the resource with ID '{id}'. Trace Id: '{aws_stack.get_trace_id()}'" - ) - return region_details.firewall_rule_group_associations.get(id) - - -def delete_firewall_rule_group_association(id): - """deletes the firewall rule group association with the given id""" - # if firewall_rule_group_associations doesn't exist it will throw an error - region_details = Route53ResolverBackend.get() - firewall_rule_group_associations = get_firewall_rule_group_association(id) - region_details.firewall_rule_group_associations.pop(id) - return firewall_rule_group_associations - - -def get_firewall_domain(id): - """returns firewall domain with the given id if it exists""" - # firewall_domain can return none - region_details = Route53ResolverBackend.get() - firewall_domain = region_details.firewall_domains.get(id) - return firewall_domain - - -def get_firewall_domain_list(id): - """returns firewall domain list with the given id if it exists""" - region_details = Route53ResolverBackend.get() - firewall_domain_list = region_details.firewall_domain_lists.get(id) - if not firewall_domain_list: - raise ResourceNotFoundException( - f"Can't find the resource with ID '{id}'. Trace Id: '{aws_stack.get_trace_id()}'" - ) - return firewall_domain_list - - -def delete_firewall_domain_list(id): - """deletes the firewall domain list with the given id""" - # if firewall_domain_lists doesn't exist it will throw an error - region_details = Route53ResolverBackend.get() - firewall_domain_list = get_firewall_domain_list(id) - region_details.firewall_domain_lists.pop(id) - return firewall_domain_list - - -def get_firewall_rule(firewall_rule_group_id, firewall_domain_list_id): - """returns firewall rule with the given id if it exists""" - region_details = Route53ResolverBackend.get() - firewall_rule = region_details.firewall_rules.get(firewall_rule_group_id, {}).get( - firewall_domain_list_id - ) - if not firewall_rule: - raise ResourceNotFoundException( - f"Can't find the resource with ID '{firewall_rule_group_id}'. Trace Id: '{aws_stack.get_trace_id()}'" - ) - return firewall_rule - - -def delete_firewall_rule(firewall_rule_group_id, firewall_domain_list_id): - """deletes the firewall rule with the given id""" - # if firewall_rules doesn't exist it will throw an error - region_details = Route53ResolverBackend.get() - firewall_rule = get_firewall_rule(firewall_rule_group_id, firewall_domain_list_id) - region_details.firewall_rules.get(firewall_rule_group_id, {}).pop(firewall_domain_list_id) - return firewall_rule + if not firewall_rule: + raise ResourceNotFoundException( + f"Can't find the resource with ID '{firewall_rule_group_id}'. Trace Id: '{aws_stack.get_trace_id()}'" + ) + return firewall_rule + + def delete_firewall_rule(self, firewall_rule_group_id, firewall_domain_list_id): + """deletes the firewall rule with the given id""" + # if firewall_rules doesn't exist it will throw an error + + firewall_rule = self.get_firewall_rule(firewall_rule_group_id, firewall_domain_list_id) + self.firewall_rules.get(firewall_rule_group_id, {}).pop(firewall_domain_list_id) + return firewall_rule + + def get_resolver_query_log_config(self, id): + """returns resolver query log config with the given id if it exists""" + + resolver_query_log_config = self.resolver_query_log_configs.get(id) + if not resolver_query_log_config: + raise ResourceNotFoundException( + f"[RSLVR-01601] The specified query logging configuration doesn't exist. Trace Id: '{aws_stack.get_trace_id()}'" + ) + return resolver_query_log_config + + def delete_resolver_query_log_config(self, id): + """deletes the resolver query log config with the given id""" + + self.get_resolver_query_log_config(id) + resolver_query_log_config = self.resolver_query_log_configs.pop(id) + return resolver_query_log_config + + def get_resolver_query_log_config_associations(self, id): + """returns resolver query log config association with the given id if it exists""" + + resolver_query_log_config_association = self.resolver_query_log_config_associations.get(id) + if not resolver_query_log_config_association: + raise ResourceNotFoundException( + f"[RSLVR-01601] The specified query logging configuration doesn't exist. Trace Id: '{aws_stack.get_trace_id()}'" + ) + return resolver_query_log_config_association + + def delete_resolver_query_log_config_associations( + self, resolver_query_log_config_id, resource_id + ): + """deletes the resolver query log config association with the given id and vpc id""" + + association_id = None + for association in self.resolver_query_log_config_associations.values(): + if not ( + association.get("ResolverQueryLogConfigId") == resolver_query_log_config_id + and association.get("ResourceId") == resource_id + ): + raise ResourceNotFoundException( + f"[RSLVR-01602] The specified query logging configuration association doesn't exist. Trace Id: '{aws_stack.get_trace_id()}'" + ) + association["Status"] = "DELETING" + association_id = association.get("Id") + return self.resolver_query_log_config_associations.pop(association_id) + + def get_or_create_firewall_config(self, resource_id, region, owner_id): + """returns the firewall config with the given id if it exists or creates a new one""" + + validate_vpc(resource_id, region) + firewall_config: FirewallConfig + if self.firewall_configs.get(resource_id): + firewall_config = self.firewall_configs[resource_id] + else: + id = get_firewall_config_id() + firewall_config = FirewallConfig( + Id=id, + ResourceId=resource_id, + OwnerId=owner_id, + FirewallFailOpen="DISABLED", + ) + self.firewall_configs[resource_id] = firewall_config + return firewall_config diff --git a/localstack/services/route53resolver/provider.py b/localstack/services/route53resolver/provider.py index 48fceeb549a3a..e29bed014deee 100644 --- a/localstack/services/route53resolver/provider.py +++ b/localstack/services/route53resolver/provider.py @@ -1,5 +1,6 @@ from datetime import datetime, timezone +from moto.ec2.models import ec2_backends from moto.route53resolver.models import Route53ResolverBackend as MotoRoute53ResolverBackend from moto.route53resolver.models import route53resolver_backends as moto_route53resolver_backends @@ -7,6 +8,7 @@ from localstack.aws.api.route53resolver import ( Action, AssociateFirewallRuleGroupResponse, + AssociateResolverQueryLogConfigResponse, BlockOverrideDnsType, BlockOverrideDomain, BlockOverrideTtl, @@ -14,58 +16,70 @@ CreateFirewallDomainListResponse, CreateFirewallRuleGroupResponse, CreateFirewallRuleResponse, + CreateResolverQueryLogConfigResponse, CreatorRequestId, DeleteFirewallDomainListResponse, DeleteFirewallRuleGroupResponse, DeleteFirewallRuleResponse, + DeleteResolverQueryLogConfigResponse, + DestinationArn, DisassociateFirewallRuleGroupResponse, + DisassociateResolverQueryLogConfigResponse, + Filters, + FirewallConfig, FirewallDomainList, FirewallDomainListMetadata, FirewallDomainName, FirewallDomains, FirewallDomainUpdateOperation, + FirewallFailOpenStatus, FirewallRule, FirewallRuleGroup, FirewallRuleGroupAssociation, FirewallRuleGroupMetadata, + GetFirewallConfigResponse, GetFirewallDomainListResponse, GetFirewallRuleGroupAssociationResponse, GetFirewallRuleGroupResponse, + GetResolverQueryLogConfigAssociationResponse, + GetResolverQueryLogConfigResponse, ListDomainMaxResults, + ListFirewallConfigsMaxResult, + ListFirewallConfigsResponse, ListFirewallDomainListsResponse, ListFirewallDomainsResponse, ListFirewallRuleGroupsResponse, ListFirewallRulesResponse, + ListResolverQueryLogConfigAssociationsResponse, + ListResolverQueryLogConfigsResponse, MaxResults, MutationProtectionStatus, Name, NextToken, Priority, + ResolverQueryLogConfig, + ResolverQueryLogConfigAssociation, + ResolverQueryLogConfigName, ResourceId, ResourceNotFoundException, Route53ResolverApi, + SortByKey, + SortOrder, TagList, + UpdateFirewallConfigResponse, UpdateFirewallDomainsResponse, UpdateFirewallRuleGroupAssociationResponse, UpdateFirewallRuleResponse, ValidationException, ) -from localstack.services.route53resolver.models import ( - Route53ResolverBackend, - delete_firewall_domain_list, - delete_firewall_rule, - delete_firewall_rule_group, - delete_firewall_rule_group_association, - get_firewall_domain, - get_firewall_domain_list, - get_firewall_rule, - get_firewall_rule_group, - get_firewall_rule_group_association, -) +from localstack.services.route53resolver.models import Route53ResolverBackend from localstack.services.route53resolver.utils import ( + get_resolver_query_log_config_id, get_route53_resolver_firewall_domain_list_id, get_route53_resolver_firewall_rule_group_association_id, get_route53_resolver_firewall_rule_group_id, + get_route53_resolver_query_log_config_association_id, + validate_destination_arn, validate_mutation_protection, validate_priority, ) @@ -106,15 +120,21 @@ def create_firewall_rule_group( def delete_firewall_rule_group( self, context: RequestContext, firewall_rule_group_id: ResourceId ) -> DeleteFirewallRuleGroupResponse: + region_details = Route53ResolverBackend.get() """Delete a Firewall Rule Group.""" - firewall_rule_group: FirewallRuleGroup = delete_firewall_rule_group(firewall_rule_group_id) + firewall_rule_group: FirewallRuleGroup = region_details.delete_firewall_rule_group( + firewall_rule_group_id + ) return DeleteFirewallRuleGroupResponse(FirewallRuleGroup=firewall_rule_group) def get_firewall_rule_group( self, context: RequestContext, firewall_rule_group_id: ResourceId ) -> GetFirewallRuleGroupResponse: + region_details = Route53ResolverBackend.get() """Get the details of a Firewall Rule Group.""" - firewall_rule_group: FirewallRuleGroup = get_firewall_rule_group(firewall_rule_group_id) + firewall_rule_group: FirewallRuleGroup = region_details.get_firewall_rule_group( + firewall_rule_group_id + ) return GetFirewallRuleGroupResponse(FirewallRuleGroup=firewall_rule_group) def list_firewall_rule_groups( @@ -159,8 +179,9 @@ def create_firewall_domain_list( def delete_firewall_domain_list( self, context: RequestContext, firewall_domain_list_id: ResourceId ) -> DeleteFirewallDomainListResponse: + region_details = Route53ResolverBackend.get() """Delete a Firewall Domain List.""" - firewall_domain_list: FirewallDomainList = delete_firewall_domain_list( + firewall_domain_list: FirewallDomainList = region_details.delete_firewall_domain_list( firewall_domain_list_id ) return DeleteFirewallDomainListResponse(FirewallDomainList=firewall_domain_list) @@ -168,8 +189,11 @@ def delete_firewall_domain_list( def get_firewall_domain_list( self, context: RequestContext, firewall_domain_list_id: ResourceId ) -> GetFirewallDomainListResponse: + region_details = Route53ResolverBackend.get() """Get the details of a Firewall Domain List.""" - firewall_domain_list: FirewallDomainList = get_firewall_domain_list(firewall_domain_list_id) + firewall_domain_list: FirewallDomainList = region_details.get_firewall_domain_list( + firewall_domain_list_id + ) return GetFirewallDomainListResponse(FirewallDomainList=firewall_domain_list) def list_firewall_domain_lists( @@ -191,12 +215,17 @@ def update_firewall_domains( operation: FirewallDomainUpdateOperation, domains: FirewallDomains, ) -> UpdateFirewallDomainsResponse: + region_details = Route53ResolverBackend.get() """Update the domains in a Firewall Domain List.""" region_details = Route53ResolverBackend.get() - firewall_domain_list: FirewallDomainList = get_firewall_domain_list(firewall_domain_list_id) + firewall_domain_list: FirewallDomainList = region_details.get_firewall_domain_list( + firewall_domain_list_id + ) - firewall_domain_list: FirewallDomainList = get_firewall_domain_list(firewall_domain_list_id) - firewall_domains = get_firewall_domain(firewall_domain_list_id) + firewall_domain_list: FirewallDomainList = region_details.get_firewall_domain_list( + firewall_domain_list_id + ) + firewall_domains = region_details.get_firewall_domain(firewall_domain_list_id) if operation == FirewallDomainUpdateOperation.ADD: if not firewall_domains: @@ -288,8 +317,9 @@ def delete_firewall_rule( firewall_rule_group_id: ResourceId, firewall_domain_list_id: ResourceId, ) -> DeleteFirewallRuleResponse: + region_details = Route53ResolverBackend.get() """Delete a firewall rule""" - firewall_rule: FirewallRule = delete_firewall_rule( + firewall_rule: FirewallRule = region_details.delete_firewall_rule( firewall_rule_group_id, firewall_domain_list_id ) return DeleteFirewallRuleResponse( @@ -332,8 +362,9 @@ def update_firewall_rule( block_override_ttl: BlockOverrideTtl = None, name: Name = None, ) -> UpdateFirewallRuleResponse: + region_details = Route53ResolverBackend.get() """Updates a firewall rule""" - firewall_rule: FirewallRule = get_firewall_rule( + firewall_rule: FirewallRule = region_details.get_firewall_rule( firewall_rule_group_id, firewall_domain_list_id ) @@ -409,9 +440,12 @@ def associate_firewall_rule_group( def disassociate_firewall_rule_group( self, context: RequestContext, firewall_rule_group_association_id: ResourceId ) -> DisassociateFirewallRuleGroupResponse: + region_details = Route53ResolverBackend.get() """Disassociate a DNS Firewall rule group from a VPC.""" firewall_rule_group_association: FirewallRuleGroupAssociation = ( - delete_firewall_rule_group_association(firewall_rule_group_association_id) + region_details.delete_firewall_rule_group_association( + firewall_rule_group_association_id + ) ) return DisassociateFirewallRuleGroupResponse( FirewallRuleGroupAssociation=firewall_rule_group_association @@ -420,9 +454,10 @@ def disassociate_firewall_rule_group( def get_firewall_rule_group_association( self, context: RequestContext, firewall_rule_group_association_id: ResourceId ) -> GetFirewallRuleGroupAssociationResponse: + region_details = Route53ResolverBackend.get() """Returns the Firewall Rule Group Association that you specified.""" firewall_rule_group_association: FirewallRuleGroupAssociation = ( - get_firewall_rule_group_association(firewall_rule_group_association_id) + region_details.get_firewall_rule_group_association(firewall_rule_group_association_id) ) return GetFirewallRuleGroupAssociationResponse( FirewallRuleGroupAssociation=firewall_rule_group_association @@ -436,12 +471,13 @@ def update_firewall_rule_group_association( mutation_protection: MutationProtectionStatus = None, name: Name = None, ) -> UpdateFirewallRuleGroupAssociationResponse: + region_details = Route53ResolverBackend.get() """Updates the specified Firewall Rule Group Association.""" validate_priority(priority=priority) validate_mutation_protection(mutation_protection=mutation_protection) firewall_rule_group_association: FirewallRuleGroupAssociation = ( - get_firewall_rule_group_association(firewall_rule_group_association_id) + region_details.get_firewall_rule_group_association(firewall_rule_group_association_id) ) if priority: @@ -455,6 +491,203 @@ def update_firewall_rule_group_association( FirewallRuleGroupAssociation=firewall_rule_group_association ) + def create_resolver_query_log_config( + self, + context: RequestContext, + name: ResolverQueryLogConfigName, + destination_arn: DestinationArn, + creator_request_id: CreatorRequestId, + tags: TagList = None, + ) -> CreateResolverQueryLogConfigResponse: + validate_destination_arn(destination_arn) + region_details = Route53ResolverBackend.get() + id = get_resolver_query_log_config_id() + arn = aws_stack.get_resolver_query_log_config_arn(id) + resolver_query_log_config: ResolverQueryLogConfig = ResolverQueryLogConfig( + Id=id, + Arn=arn, + Name=name, + AssociationCount=0, + Status="CREATED", + OwnerId=context.account_id, + ShareStatus="NOT_SHARED", + DestinationArn=destination_arn, + CreatorRequestId=creator_request_id, + CreationTime=datetime.now(timezone.utc).isoformat(), + ) + region_details.resolver_query_log_configs[id] = resolver_query_log_config + moto_route53resolver_backends[context.region].tagger.tag_resource(arn, tags or []) + return CreateResolverQueryLogConfigResponse( + ResolverQueryLogConfig=resolver_query_log_config + ) + + def get_resolver_query_log_config( + self, context: RequestContext, resolver_query_log_config_id: ResourceId + ) -> GetResolverQueryLogConfigResponse: + region_details = Route53ResolverBackend.get() + resolver_query_log_config: ResolverQueryLogConfig = ( + region_details.get_resolver_query_log_config(resolver_query_log_config_id) + ) + return GetResolverQueryLogConfigResponse(ResolverQueryLogConfig=resolver_query_log_config) + + def delete_resolver_query_log_config( + self, context: RequestContext, resolver_query_log_config_id: ResourceId + ) -> DeleteResolverQueryLogConfigResponse: + region_details = Route53ResolverBackend.get() + resolver_query_log_config: ResolverQueryLogConfig = ( + region_details.delete_resolver_query_log_config(resolver_query_log_config_id) + ) + return DeleteResolverQueryLogConfigResponse( + ResolverQueryLogConfig=resolver_query_log_config + ) + + def list_resolver_query_log_configs( + self, + context: RequestContext, + max_results: MaxResults = None, + next_token: NextToken = None, + filters: Filters = None, + sort_by: SortByKey = None, + sort_order: SortOrder = None, + ) -> ListResolverQueryLogConfigsResponse: + region_details = Route53ResolverBackend.get() + resolver_query_log_configs = [] + for resolver_query_log_config in region_details.resolver_query_log_configs.values(): + resolver_query_log_configs.append(ResolverQueryLogConfig(resolver_query_log_config)) + return ListResolverQueryLogConfigsResponse( + ResolverQueryLogConfigs=resolver_query_log_configs, + TotalCount=len(resolver_query_log_configs), + ) + + def associate_resolver_query_log_config( + self, + context: RequestContext, + resolver_query_log_config_id: ResourceId, + resource_id: ResourceId, + ) -> AssociateResolverQueryLogConfigResponse: + + region_details = Route53ResolverBackend.get() + id = get_route53_resolver_query_log_config_association_id() + + resolver_query_log_config_association: ResolverQueryLogConfigAssociation = ( + ResolverQueryLogConfigAssociation( + Id=id, + ResolverQueryLogConfigId=resolver_query_log_config_id, + ResourceId=resource_id, + Status="ACTIVE", + Error="NONE", + ErrorMessage="", + CreationTime=datetime.now(timezone.utc).isoformat(), + ) + ) + + region_details.resolver_query_log_config_associations[ + id + ] = resolver_query_log_config_association + + return AssociateResolverQueryLogConfigResponse( + ResolverQueryLogConfigAssociation=resolver_query_log_config_association + ) + + def disassociate_resolver_query_log_config( + self, + context: RequestContext, + resolver_query_log_config_id: ResourceId, + resource_id: ResourceId, + ) -> DisassociateResolverQueryLogConfigResponse: + region_details = Route53ResolverBackend.get() + resolver_query_log_config_association = ( + region_details.delete_resolver_query_log_config_associations( + resolver_query_log_config_id, resource_id + ) + ) + + return DisassociateResolverQueryLogConfigResponse( + ResolverQueryLogConfigAssociation=resolver_query_log_config_association + ) + + def get_resolver_query_log_config_association( + self, context: RequestContext, resolver_query_log_config_association_id: ResourceId + ) -> GetResolverQueryLogConfigAssociationResponse: + region_details = Route53ResolverBackend.get() + resolver_query_log_config_association: ResolverQueryLogConfigAssociation = ( + region_details.get_resolver_query_log_config_associations( + resolver_query_log_config_association_id + ) + ) + return GetResolverQueryLogConfigAssociationResponse( + ResolverQueryLogConfigAssociation=resolver_query_log_config_association + ) + + def list_resolver_query_log_config_associations( + self, + context: RequestContext, + max_results: MaxResults = None, + next_token: NextToken = None, + filters: Filters = None, + sort_by: SortByKey = None, + sort_order: SortOrder = None, + ) -> ListResolverQueryLogConfigAssociationsResponse: + region_details = Route53ResolverBackend.get() + + resolver_query_log_config_associations = [] + for ( + resolver_query_log_config_association + ) in region_details.resolver_query_log_config_associations.values(): + resolver_query_log_config_associations.append( + ResolverQueryLogConfigAssociation(resolver_query_log_config_association) + ) + return ListResolverQueryLogConfigAssociationsResponse( + TotalCount=len(resolver_query_log_config_associations), + ResolverQueryLogConfigAssociations=resolver_query_log_config_associations, + ) + + def get_firewall_config( + self, context: RequestContext, resource_id: ResourceId + ) -> GetFirewallConfigResponse: + region_details = Route53ResolverBackend.get() + firewall_config = region_details.get_or_create_firewall_config( + resource_id, context.region, context.account_id + ) + return GetFirewallConfigResponse(FirewallConfig=firewall_config) + + def list_firewall_configs( + self, + context: RequestContext, + max_results: ListFirewallConfigsMaxResult = None, + next_token: NextToken = None, + ) -> ListFirewallConfigsResponse: + region_details = Route53ResolverBackend.get() + firewall_configs = [] + backend = ec2_backends[context.region] + for vpc in backend.vpcs: + if vpc not in region_details.firewall_configs: + region_details.get_or_create_firewall_config( + vpc, context.region, context.account_id + ) + for firewall_config in region_details.firewall_configs.values(): + firewall_configs.append(select_from_typed_dict(FirewallConfig, firewall_config)) + return ListFirewallConfigsResponse(FirewallConfigs=firewall_configs) + + def update_firewall_config( + self, + context: RequestContext, + resource_id: ResourceId, + firewall_fail_open: FirewallFailOpenStatus, + ) -> UpdateFirewallConfigResponse: + region_details = Route53ResolverBackend.get() + backend = ec2_backends[context.region] + for resource_id in backend.vpcs: + if resource_id not in region_details.firewall_configs: + firewall_config = region_details.get_or_create_firewall_config( + resource_id, context.region, context.account_id + ) + firewall_config["FirewallFailOpen"] = firewall_fail_open + else: + firewall_config = region_details.firewall_configs[resource_id] + firewall_config["FirewallFailOpen"] = firewall_fail_open + return UpdateFirewallConfigResponse(FirewallConfig=firewall_config) + @patch(MotoRoute53ResolverBackend._matched_arn) def Route53ResolverBackend_matched_arn(fn, self, resource_arn): @@ -469,4 +702,7 @@ def Route53ResolverBackend_matched_arn(fn, self, resource_arn): for firewall_rule_group_association in region_details.firewall_rule_group_associations.values(): if firewall_rule_group_association.get("Arn") == resource_arn: return + for resolver_query_log_config in region_details.resolver_query_log_configs.values(): + if resolver_query_log_config.get("Arn") == resource_arn: + return fn(self, resource_arn) diff --git a/localstack/services/route53resolver/utils.py b/localstack/services/route53resolver/utils.py index 9edcce4fc25c0..0f1b35a85b8cc 100644 --- a/localstack/services/route53resolver/utils.py +++ b/localstack/services/route53resolver/utils.py @@ -1,4 +1,8 @@ -from localstack.aws.api.route53resolver import ValidationException +import re + +from moto.ec2 import ec2_backends + +from localstack.aws.api.route53resolver import ResourceNotFoundException, ValidationException from localstack.utils.aws import aws_stack from localstack.utils.strings import get_random_hex @@ -15,6 +19,18 @@ def get_route53_resolver_firewall_rule_group_association_id(): return f"rslvr-frgassoc-{get_random_hex(17)}" +def get_resolver_query_log_config_id(): + return f"rslvr-rqlc-{get_random_hex(17)}" + + +def get_route53_resolver_query_log_config_association_id(): + return f"rslvr-qlcassoc-{get_random_hex(17)}" + + +def get_firewall_config_id(): + return f"rslvr-fc-{get_random_hex(17)}" + + def validate_priority(priority): # value of priority can be null in case of update if priority: @@ -30,3 +46,20 @@ def validate_mutation_protection(mutation_protection): raise ValidationException( f"[RSLVR-02018] The mutation protection value you provided is reserved. Provide a value of 'ENABLED' or 'DISABLED'. Trace Id: '{aws_stack.get_trace_id()}'" ) + + +def validate_destination_arn(destination_arn): + arn_pattern = r"arn:aws:(kinesis|logs|s3):?(.*)" + if not re.match(arn_pattern, destination_arn): + raise ResourceNotFoundException( + f"[RSLVR-01014] An Amazon Resource Name (ARN) for the destination is required. Trace Id: '{aws_stack.get_trace_id()}'" + ) + + +def validate_vpc(vpc_id, region): + backend = ec2_backends[region] + + if vpc_id not in backend.vpcs: + raise ValidationException( + f"[RSLVR-02025] Can't find the resource with ID : '{vpc_id}'. Trace Id: '{aws_stack.get_trace_id()}'" + ) diff --git a/localstack/utils/aws/aws_stack.py b/localstack/utils/aws/aws_stack.py index 3f2626292654e..9996dfc8d8372 100644 --- a/localstack/utils/aws/aws_stack.py +++ b/localstack/utils/aws/aws_stack.py @@ -1326,3 +1326,8 @@ def get_route53_resolver_firewall_rule_group_associations_arn( def get_trace_id(): return f"1-{get_random_hex(8)}-{get_random_hex(24)}" + + +def get_resolver_query_log_config_arn(id: str, account_id: str = None, region_name: str = None): + pattern = "arn:aws:route53resolver:%s:%s:resolver-query-log-config/%s" + return _resource_arn(id, pattern, account_id=account_id, region_name=region_name) diff --git a/tests/terraform/run.sh b/tests/terraform/run.sh new file mode 100644 index 0000000000000..d4e9beaf06b0e --- /dev/null +++ b/tests/terraform/run.sh @@ -0,0 +1,55 @@ +#!/bin/bash + +export TF_ACC=1 +export AWS_ALTERNATE_ACCESS_KEY_ID=test +export AWS_ALTERNATE_SECRET_ACCESS_KEY=test +export AWS_ALTERNATE_REGION=us-east-2 +export AWS_DEFAULT_REGION=us-east-1 +export AWS_ACCESS_KEY_ID=test +export AWS_SECRET_ACCESS_KEY=test + +# in some services we can only create certain endpoints before quota updation +# so we need restrict no of tests that can run in parallel +PARALLELISM_MAPPING=( "route53resolver:4") +# example to extend the mapping: +# PARALLELISM_MAPPING=( "route53resolver:4" +# "s3:2" +# "ec2:10" ) + +PARALLEL=0 +for service in "${PARALLELISM_MAPPING[@]}" ; do + KEY="${service%%:*}" + if [ $KEY == $1 ]; then + VALUE="${service##*:}" + PARALLEL=1 + fi +done + +cd terraform-provider-aws + +if [ $# == 2 ]; then + echo "Service: $1 | Test: $2" + if [ $PARALLEL == 1 ]; then + echo "Parallelism: $VALUE" + go test ./internal/service/$1 -test.count 1 -test.v -test.timeout 60m -parallel $VALUE -run $2 + else + echo "Parallelism: Auto" + go test ./internal/service/$1 -test.count 1 -test.v -test.timeout 60m -run $2 + fi +elif [ $# == 1 ]; then + echo "Service: $1 | Test: All" + if [ $PARALLEL == 1 ]; then + echo "Parallelism: $VALUE" + go test ./internal/service/$1 -test.count 1 -test.v -test.timeout 60m -parallel $VALUE + else + echo "Parallelism: Auto" + go test ./internal/service/$1 -test.count 1 -test.v -test.timeout 60m + fi +else + echo "usage: ./run.sh service_name [test_case_pattern]" + exit 1 +fi + +if [ $CI == "false" ]; then + python -m http.server +fi \ No newline at end of file diff --git a/tests/terraform/terraform-tests.yaml b/tests/terraform/terraform-tests.yaml index 56d3493e74241..20e9a58cbbed8 100644 --- a/tests/terraform/terraform-tests.yaml +++ b/tests/terraform/terraform-tests.yaml @@ -69,34 +69,35 @@ route53: - TestAccRoute53TrafficPolicyDocumentDataSource_basic - TestAccRoute53TrafficPolicyDocumentDataSource_complete route53resolver: - - TestAccRoute53ResolverFirewallRuleGroup_basic - - TestAccRoute53ResolverFirewallRuleGroup_disappears - - TestAccRoute53ResolverFirewallRuleGroup_tags - TestAccRoute53ResolverEndpointDataSource_basic - TestAccRoute53ResolverEndpointDataSource_filter + - TestAccRoute53ResolverEndpoint_basicInbound + - TestAccRoute53ResolverEndpoint_updateOutbound + - TestAccRoute53ResolverFirewallConfig_basic + - TestAccRoute53ResolverFirewallConfig_disappears + - TestAccRoute53ResolverFirewallDomainList_basic + - TestAccRoute53ResolverFirewallDomainList_disappears + - TestAccRoute53ResolverFirewallDomainList_domains + - TestAccRoute53ResolverFirewallDomainList_tags - TestAccRoute53ResolverFirewallRuleGroupAssociation_basic - - TestAccRoute53ResolverFirewallRuleGroupAssociation_name + - TestAccRoute53ResolverFirewallRuleGroupAssociation_disappears - TestAccRoute53ResolverFirewallRuleGroupAssociation_mutationProtection + - TestAccRoute53ResolverFirewallRuleGroupAssociation_name - TestAccRoute53ResolverFirewallRuleGroupAssociation_priority - - TestAccRoute53ResolverFirewallRuleGroupAssociation_disappears - TestAccRoute53ResolverFirewallRuleGroupAssociation_tags - - TestAccRoute53ResolverRule_forwardEndpointRecreate - - TestAccRoute53ResolverEndpoint_basicInbound - - TestAccRoute53ResolverEndpoint_updateOutbound - - TestAccRoute53ResolverRuleDataSource_sharedByMe - - TestAccRoute53ResolverRuleDataSource_sharedWithMe + - TestAccRoute53ResolverFirewallRuleGroup_basic + - TestAccRoute53ResolverFirewallRuleGroup_disappears + - TestAccRoute53ResolverFirewallRuleGroup_tags - TestAccRoute53ResolverFirewallRule_basic - TestAccRoute53ResolverFirewallRule_block - TestAccRoute53ResolverFirewallRule_blockOverride - TestAccRoute53ResolverFirewallRule_disappears - - TestAccRoute53ResolverRuleAssociation_basic - - TestAccRoute53ResolverRulesDataSource_resolverEndpointID - - TestAccRoute53ResolverRulesDataSource_nameRegex - - TestAccRoute53ResolverRulesDataSource_nonExistentNameRegex - - TestAccRoute53ResolverFirewallDomainList_basic - - TestAccRoute53ResolverFirewallDomainList_domains - - TestAccRoute53ResolverFirewallDomainList_disappears - - TestAccRoute53ResolverFirewallDomainList_tags + - TestAccRoute53ResolverQueryLogConfigAssociation_basic + - TestAccRoute53ResolverQueryLogConfigAssociation_disappears + - TestAccRoute53ResolverQueryLogConfig_basic + - TestAccRoute53ResolverQueryLogConfig_disappears + - TestAccRoute53ResolverQueryLogConfig_tags + - TestValidResolverName s3: - TestAccS3BucketVersioning_basic - TestAccS3BucketVersioning_disappears