diff --git a/CHANGELOG.md b/CHANGELOG.md index 099cf063b..6008ceaf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,51 @@ Moto Changelog ============== +3.1.14 +----- +Docker Digest for 3.1.14: + + New Methods: + * Greengrass: + * create_function_definition() + * create_resource_definition() + * create_function_definition_version() + * create_resource_definition_version() + * create_subscription_definition() + * create_subscription_definition_version() + * delete_function_definition() + * delete_resource_definition() + * delete_subscription_definition() + * get_function_definition() + * get_function_definition_version() + * get_resource_definition() + * get_resource_definition_version() + * get_subscription_definition() + * get_subscription_definition_version() + * list_function_definitions() + * list_function_definition_versions() + * list_resource_definitions() + * list_resource_definition_versions() + * list_subscription_definitions() + * list_subscription_definition_versions() + * update_function_definition() + * update_resource_definition() + * update_subscription_definition() + * EKS: + * list_tags_for_resources() + * tag_resource() + * untag_resource() + * Route53: + * associate_vpc() + * disassociate_vpc_from_hosted_zone() + * update_health_check() + * update_hosted_zone_comment() + + Miscellaneous: + * APIGateway:put_integration() now supports the requestParameters-parameter + * EC2:create_route() now validates whether a route already exists + + 3.1.13 ----- Docker Digest for 3.1.13: _sha256:d7f6c779c79f03b686747ae26b52bdca26fd81a50c6a41a8a6cba50c96982abf_ diff --git a/IMPLEMENTATION_COVERAGE.md b/IMPLEMENTATION_COVERAGE.md index 5c2a07ca2..b1d02ff9b 100644 --- a/IMPLEMENTATION_COVERAGE.md +++ b/IMPLEMENTATION_COVERAGE.md @@ -2114,7 +2114,7 @@ ## eks
-35% implemented +44% implemented - [ ] associate_encryption_config - [ ] associate_identity_provider_config @@ -2140,11 +2140,11 @@ - [X] list_fargate_profiles - [ ] list_identity_provider_configs - [X] list_nodegroups -- [ ] list_tags_for_resource +- [X] list_tags_for_resource - [ ] list_updates - [ ] register_cluster -- [ ] tag_resource -- [ ] untag_resource +- [X] tag_resource +- [X] untag_resource - [ ] update_addon - [ ] update_cluster_config - [ ] update_cluster_version @@ -2867,7 +2867,7 @@ ## greengrass
-17% implemented +43% implemented - [ ] associate_role_to_group - [ ] associate_service_role_to_account @@ -2878,26 +2878,26 @@ - [ ] create_deployment - [X] create_device_definition - [X] create_device_definition_version -- [ ] create_function_definition -- [ ] create_function_definition_version +- [X] create_function_definition +- [X] create_function_definition_version - [ ] create_group - [ ] create_group_certificate_authority - [ ] create_group_version - [ ] create_logger_definition - [ ] create_logger_definition_version -- [ ] create_resource_definition -- [ ] create_resource_definition_version +- [X] create_resource_definition +- [X] create_resource_definition_version - [ ] create_software_update_job -- [ ] create_subscription_definition -- [ ] create_subscription_definition_version +- [X] create_subscription_definition +- [X] create_subscription_definition_version - [ ] delete_connector_definition - [X] delete_core_definition - [X] delete_device_definition -- [ ] delete_function_definition +- [X] delete_function_definition - [ ] delete_group - [ ] delete_logger_definition -- [ ] delete_resource_definition -- [ ] delete_subscription_definition +- [X] delete_resource_definition +- [X] delete_subscription_definition - [ ] disassociate_role_from_group - [ ] disassociate_service_role_from_account - [ ] get_associated_role @@ -2910,19 +2910,19 @@ - [ ] get_deployment_status - [X] get_device_definition - [X] get_device_definition_version -- [ ] get_function_definition -- [ ] get_function_definition_version +- [X] get_function_definition +- [X] get_function_definition_version - [ ] get_group - [ ] get_group_certificate_authority - [ ] get_group_certificate_configuration - [ ] get_group_version - [ ] get_logger_definition - [ ] get_logger_definition_version -- [ ] get_resource_definition -- [ ] get_resource_definition_version +- [X] get_resource_definition +- [X] get_resource_definition_version - [ ] get_service_role_for_account -- [ ] get_subscription_definition -- [ ] get_subscription_definition_version +- [X] get_subscription_definition +- [X] get_subscription_definition_version - [ ] get_thing_runtime_configuration - [ ] list_bulk_deployment_detailed_reports - [ ] list_bulk_deployments @@ -2933,17 +2933,17 @@ - [ ] list_deployments - [X] list_device_definition_versions - [X] list_device_definitions -- [ ] list_function_definition_versions -- [ ] list_function_definitions +- [X] list_function_definition_versions +- [X] list_function_definitions - [ ] list_group_certificate_authorities - [ ] list_group_versions - [ ] list_groups - [ ] list_logger_definition_versions - [ ] list_logger_definitions -- [ ] list_resource_definition_versions -- [ ] list_resource_definitions -- [ ] list_subscription_definition_versions -- [ ] list_subscription_definitions +- [X] list_resource_definition_versions +- [X] list_resource_definitions +- [X] list_subscription_definition_versions +- [X] list_subscription_definitions - [ ] list_tags_for_resource - [ ] reset_deployments - [ ] start_bulk_deployment @@ -2954,19 +2954,20 @@ - [ ] update_connector_definition - [X] update_core_definition - [X] update_device_definition -- [ ] update_function_definition +- [X] update_function_definition - [ ] update_group - [ ] update_group_certificate_configuration - [ ] update_logger_definition -- [ ] update_resource_definition -- [ ] update_subscription_definition +- [X] update_resource_definition +- [X] update_subscription_definition - [ ] update_thing_runtime_configuration
## guardduty
-18% implemented +17% implemented +- [ ] accept_administrator_invitation - [ ] accept_invitation - [ ] archive_findings - [X] create_detector @@ -2987,9 +2988,11 @@ - [ ] describe_organization_configuration - [ ] describe_publishing_destination - [ ] disable_organization_admin_account +- [ ] disassociate_from_administrator_account - [ ] disassociate_from_master_account - [ ] disassociate_members - [X] enable_organization_admin_account +- [ ] get_administrator_account - [X] get_detector - [X] get_filter - [ ] get_findings @@ -2999,6 +3002,7 @@ - [ ] get_master_account - [ ] get_member_detectors - [ ] get_members +- [ ] get_remaining_free_trial_days - [ ] get_threat_intel_set - [ ] get_usage_statistics - [ ] invite_members @@ -4733,10 +4737,10 @@ ## route53
-34% implemented +40% implemented - [ ] activate_key_signing_key -- [ ] associate_vpc_with_hosted_zone +- [X] associate_vpc_with_hosted_zone - [ ] change_cidr_collection - [X] change_resource_record_sets - [X] change_tags_for_resource @@ -4761,7 +4765,7 @@ - [ ] delete_traffic_policy_instance - [ ] delete_vpc_association_authorization - [ ] disable_hosted_zone_dnssec -- [ ] disassociate_vpc_from_hosted_zone +- [X] disassociate_vpc_from_hosted_zone - [ ] enable_hosted_zone_dnssec - [ ] get_account_limit - [ ] get_change @@ -4801,8 +4805,8 @@ - [ ] list_traffic_policy_versions - [ ] list_vpc_association_authorizations - [ ] test_dns_answer -- [ ] update_health_check -- [ ] update_hosted_zone_comment +- [X] update_health_check +- [X] update_hosted_zone_comment - [ ] update_traffic_policy_comment - [ ] update_traffic_policy_instance
@@ -5998,6 +6002,7 @@ - compute-optimizer - connect - connect-contact-lens +- connectcampaigns - connectparticipant - cur - customer-profiles @@ -6110,6 +6115,7 @@ - qldb-session - rbin - rds-data +- redshiftserverless - resiliencehub - robomaker - route53-recovery-cluster diff --git a/docs/docs/services/cognito-idp.rst b/docs/docs/services/cognito-idp.rst index 3065ea664..6e63a2af8 100644 --- a/docs/docs/services/cognito-idp.rst +++ b/docs/docs/services/cognito-idp.rst @@ -12,6 +12,8 @@ cognito-idp =========== +.. autoclass:: moto.cognitoidp.models.CognitoIdpBackend + |start-h3| Example usage |end-h3| .. sourcecode:: python @@ -145,10 +147,3 @@ cognito-idp - [ ] verify_user_attribute - -|start-h3| Stable Cognito User Pool Id |end-h3| - -In some cases, you need to have reproducible IDs for the user pool. -For example, a single initialization before the start of integration tests. - -This behavior can be enabled by passing the environment variable: MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY=HASH. diff --git a/docs/docs/services/eks.rst b/docs/docs/services/eks.rst index 00fad92b5..1a4a99320 100644 --- a/docs/docs/services/eks.rst +++ b/docs/docs/services/eks.rst @@ -50,10 +50,22 @@ eks - [ ] list_identity_provider_configs - [X] list_nodegroups - [X] list_tags_for_resource + + This function currently will list tags on an EKS cluster only. It does not list tags from a managed node group + + - [ ] list_updates - [ ] register_cluster - [X] tag_resource + + This function currently will tag an EKS cluster only. It does not tag a managed node group + + - [X] untag_resource + + This function currently will remove tags on an EKS cluster only. It does not remove tags from a managed node group + + - [ ] update_addon - [ ] update_cluster_config - [ ] update_cluster_version diff --git a/docs/docs/services/greengrass.rst b/docs/docs/services/greengrass.rst index 315fdc543..d27ddab3b 100644 --- a/docs/docs/services/greengrass.rst +++ b/docs/docs/services/greengrass.rst @@ -34,26 +34,26 @@ greengrass - [ ] create_deployment - [X] create_device_definition - [X] create_device_definition_version -- [ ] create_function_definition -- [ ] create_function_definition_version +- [X] create_function_definition +- [X] create_function_definition_version - [ ] create_group - [ ] create_group_certificate_authority - [ ] create_group_version - [ ] create_logger_definition - [ ] create_logger_definition_version -- [ ] create_resource_definition -- [ ] create_resource_definition_version +- [X] create_resource_definition +- [X] create_resource_definition_version - [ ] create_software_update_job -- [ ] create_subscription_definition -- [ ] create_subscription_definition_version +- [X] create_subscription_definition +- [X] create_subscription_definition_version - [ ] delete_connector_definition - [X] delete_core_definition - [X] delete_device_definition -- [ ] delete_function_definition +- [X] delete_function_definition - [ ] delete_group - [ ] delete_logger_definition -- [ ] delete_resource_definition -- [ ] delete_subscription_definition +- [X] delete_resource_definition +- [X] delete_subscription_definition - [ ] disassociate_role_from_group - [ ] disassociate_service_role_from_account - [ ] get_associated_role @@ -66,19 +66,19 @@ greengrass - [ ] get_deployment_status - [X] get_device_definition - [X] get_device_definition_version -- [ ] get_function_definition -- [ ] get_function_definition_version +- [X] get_function_definition +- [X] get_function_definition_version - [ ] get_group - [ ] get_group_certificate_authority - [ ] get_group_certificate_configuration - [ ] get_group_version - [ ] get_logger_definition - [ ] get_logger_definition_version -- [ ] get_resource_definition -- [ ] get_resource_definition_version +- [X] get_resource_definition +- [X] get_resource_definition_version - [ ] get_service_role_for_account -- [ ] get_subscription_definition -- [ ] get_subscription_definition_version +- [X] get_subscription_definition +- [X] get_subscription_definition_version - [ ] get_thing_runtime_configuration - [ ] list_bulk_deployment_detailed_reports - [ ] list_bulk_deployments @@ -89,17 +89,17 @@ greengrass - [ ] list_deployments - [X] list_device_definition_versions - [X] list_device_definitions -- [ ] list_function_definition_versions -- [ ] list_function_definitions +- [X] list_function_definition_versions +- [X] list_function_definitions - [ ] list_group_certificate_authorities - [ ] list_group_versions - [ ] list_groups - [ ] list_logger_definition_versions - [ ] list_logger_definitions -- [ ] list_resource_definition_versions -- [ ] list_resource_definitions -- [ ] list_subscription_definition_versions -- [ ] list_subscription_definitions +- [X] list_resource_definition_versions +- [X] list_resource_definitions +- [X] list_subscription_definition_versions +- [X] list_subscription_definitions - [ ] list_tags_for_resource - [ ] reset_deployments - [ ] start_bulk_deployment @@ -110,11 +110,11 @@ greengrass - [ ] update_connector_definition - [X] update_core_definition - [X] update_device_definition -- [ ] update_function_definition +- [X] update_function_definition - [ ] update_group - [ ] update_group_certificate_configuration - [ ] update_logger_definition -- [ ] update_resource_definition -- [ ] update_subscription_definition +- [X] update_resource_definition +- [X] update_subscription_definition - [ ] update_thing_runtime_configuration diff --git a/docs/docs/services/guardduty.rst b/docs/docs/services/guardduty.rst index 143acf6a0..3bb95e38a 100644 --- a/docs/docs/services/guardduty.rst +++ b/docs/docs/services/guardduty.rst @@ -25,6 +25,7 @@ guardduty |start-h3| Implemented features for this service |end-h3| +- [ ] accept_administrator_invitation - [ ] accept_invitation - [ ] archive_findings - [X] create_detector @@ -45,9 +46,11 @@ guardduty - [ ] describe_organization_configuration - [ ] describe_publishing_destination - [ ] disable_organization_admin_account +- [ ] disassociate_from_administrator_account - [ ] disassociate_from_master_account - [ ] disassociate_members - [X] enable_organization_admin_account +- [ ] get_administrator_account - [X] get_detector - [X] get_filter - [ ] get_findings @@ -57,6 +60,7 @@ guardduty - [ ] get_master_account - [ ] get_member_detectors - [ ] get_members +- [ ] get_remaining_free_trial_days - [ ] get_threat_intel_set - [ ] get_usage_statistics - [ ] invite_members diff --git a/docs/docs/services/route53.rst b/docs/docs/services/route53.rst index d00b7bf97..148621fd2 100644 --- a/docs/docs/services/route53.rst +++ b/docs/docs/services/route53.rst @@ -26,7 +26,7 @@ route53 |start-h3| Implemented features for this service |end-h3| - [ ] activate_key_signing_key -- [ ] associate_vpc_with_hosted_zone +- [X] associate_vpc_with_hosted_zone - [ ] change_cidr_collection - [X] change_resource_record_sets - [X] change_tags_for_resource @@ -55,7 +55,7 @@ route53 - [ ] delete_traffic_policy_instance - [ ] delete_vpc_association_authorization - [ ] disable_hosted_zone_dnssec -- [ ] disassociate_vpc_from_hosted_zone +- [X] disassociate_vpc_from_hosted_zone - [ ] enable_hosted_zone_dnssec - [ ] get_account_limit - [ ] get_change @@ -111,8 +111,8 @@ route53 - [ ] list_traffic_policy_versions - [ ] list_vpc_association_authorizations - [ ] test_dns_answer -- [ ] update_health_check -- [ ] update_hosted_zone_comment +- [X] update_health_check +- [X] update_hosted_zone_comment - [ ] update_traffic_policy_comment - [ ] update_traffic_policy_instance diff --git a/docs/requirements.txt b/docs/requirements.txt index a12d5bd7f..df387da4d 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -6,3 +6,4 @@ readthedocs-sphinx-search==0.1.1 docker openapi_spec_validator PyYAML>=5.1 +python-jose[cryptography]>=3.1.0,<4.0.0 diff --git a/moto/cognitoidp/models.py b/moto/cognitoidp/models.py index 276effb5d..87f823970 100644 --- a/moto/cognitoidp/models.py +++ b/moto/cognitoidp/models.py @@ -831,6 +831,13 @@ class CognitoResourceServer(BaseModel): class CognitoIdpBackend(BaseBackend): + """ + In some cases, you need to have reproducible IDs for the user pool. + For example, a single initialization before the start of integration tests. + + This behavior can be enabled by passing the environment variable: MOTO_COGNITO_IDP_USER_POOL_ID_STRATEGY=HASH. + """ + def __init__(self, region_name, account_id): super().__init__(region_name, account_id) self.user_pools = OrderedDict() diff --git a/moto/route53/models.py b/moto/route53/models.py index 8e6be2409..db713cbe9 100644 --- a/moto/route53/models.py +++ b/moto/route53/models.py @@ -470,14 +470,14 @@ class Route53Backend(BaseBackend): # check if hosted zone exists self.get_hosted_zone(zone_id) - def associate_vpc(self, zone_id, vpcid, vpcregion): + def associate_vpc_with_hosted_zone(self, zone_id, vpcid, vpcregion): zone = self.get_hosted_zone(zone_id) if not zone.private_zone: raise PublicZoneVPCAssociation() zone.add_vpc(vpcid, vpcregion) return zone - def disassociate_vpc(self, zone_id, vpcid): + def disassociate_vpc_from_hosted_zone(self, zone_id, vpcid): zone = self.get_hosted_zone(zone_id) if len(zone.vpcs) <= 1: raise LastVPCAssociation() diff --git a/moto/route53/responses.py b/moto/route53/responses.py index 86ea8302c..01ca96431 100644 --- a/moto/route53/responses.py +++ b/moto/route53/responses.py @@ -151,7 +151,7 @@ class Route53(BaseResponse): vpcid = vpc.get("VPCId", None) vpcregion = vpc.get("VPCRegion", None) - route53_backend.associate_vpc(zoneid, vpcid, vpcregion) + route53_backend.associate_vpc_with_hosted_zone(zoneid, vpcid, vpcregion) template = Template(ASSOCIATE_VPC_RESPONSE) return 200, headers, template.render(comment=comment) @@ -169,7 +169,7 @@ class Route53(BaseResponse): vpc = elements.get("DisassociateVPCFromHostedZoneRequest", {}).get("VPC", {}) vpcid = vpc.get("VPCId", None) - route53_backend.disassociate_vpc(zoneid, vpcid) + route53_backend.disassociate_vpc_from_hosted_zone(zoneid, vpcid) template = Template(DISASSOCIATE_VPC_RESPONSE) return 200, headers, template.render(comment=comment) diff --git a/scripts/implementation_coverage.py b/scripts/implementation_coverage.py index 389972cb8..2a3080d9b 100755 --- a/scripts/implementation_coverage.py +++ b/scripts/implementation_coverage.py @@ -261,7 +261,7 @@ def write_implementation_coverage_to_docs(coverage): file.write(" ...\n") file.write("\n\n") file.write(".. sourcecode:: python\n\n") - file.write(" @mock_all\n") + file.write(" @mock_all()\n") file.write(" def test_all_supported_services_at_the_same_time():\n") file.write(" ...\n") file.write("\n") diff --git a/tests/test_route53/test_route53.py b/tests/test_route53/test_route53.py index 6a17b9ed0..749264be6 100644 --- a/tests/test_route53/test_route53.py +++ b/tests/test_route53/test_route53.py @@ -161,6 +161,20 @@ def test_get_unknown_hosted_zone(): err["Message"].should.equal("No hosted zone found with ID: unknown") +@mock_route53 +def test_update_hosted_zone_comment(): + conn = boto3.client("route53", region_name="us-east-1") + response = conn.create_hosted_zone( + Name="testdns.aws.com.", CallerReference=str(hash("foo")) + ) + zone_id = response["HostedZone"]["Id"].split("/")[-1] + + conn.update_hosted_zone_comment(Id=zone_id, Comment="yolo") + + resp = conn.get_hosted_zone(Id=zone_id)["HostedZone"] + resp["Config"].should.have.key("Comment").equals("yolo") + + @mock_route53 def test_list_resource_record_set_unknown_zone(): conn = boto3.client("route53", region_name="us-east-1") @@ -188,90 +202,6 @@ def test_list_resource_record_set_unknown_type(): err["Message"].should.equal("Bad Request") -@mock_route53 -def test_create_health_check(): - conn = boto3.client("route53", region_name="us-east-1") - - check = conn.create_health_check( - CallerReference="?", - HealthCheckConfig={ - "IPAddress": "10.0.0.25", - "Port": 80, - "Type": "HTTP", - "ResourcePath": "/", - "FullyQualifiedDomainName": "example.com", - "SearchString": "a good response", - "RequestInterval": 10, - "FailureThreshold": 2, - }, - )["HealthCheck"] - check.should.have.key("Id").match("[a-z0-9-]+") - check.should.have.key("CallerReference") - check.should.have.key("HealthCheckConfig") - check["HealthCheckConfig"].should.have.key("IPAddress").equal("10.0.0.25") - check["HealthCheckConfig"].should.have.key("Port").equal(80) - check["HealthCheckConfig"].should.have.key("Type").equal("HTTP") - check["HealthCheckConfig"].should.have.key("ResourcePath").equal("/") - check["HealthCheckConfig"].should.have.key("FullyQualifiedDomainName").equal( - "example.com" - ) - check["HealthCheckConfig"].should.have.key("SearchString").equal("a good response") - check["HealthCheckConfig"].should.have.key("RequestInterval").equal(10) - check["HealthCheckConfig"].should.have.key("FailureThreshold").equal(2) - check.should.have.key("HealthCheckVersion").equal(1) - - -@mock_route53 -def test_list_health_checks(): - conn = boto3.client("route53", region_name="us-east-1") - - conn.list_health_checks()["HealthChecks"].should.have.length_of(0) - - check = conn.create_health_check( - CallerReference="?", - HealthCheckConfig={ - "IPAddress": "10.0.0.25", - "Port": 80, - "Type": "HTTP", - "ResourcePath": "/", - "FullyQualifiedDomainName": "example.com", - "SearchString": "a good response", - "RequestInterval": 10, - "FailureThreshold": 2, - }, - )["HealthCheck"] - - checks = conn.list_health_checks()["HealthChecks"] - checks.should.have.length_of(1) - checks.should.contain(check) - - -@mock_route53 -def test_delete_health_checks(): - conn = boto3.client("route53", region_name="us-east-1") - - conn.list_health_checks()["HealthChecks"].should.have.length_of(0) - - check = conn.create_health_check( - CallerReference="?", - HealthCheckConfig={ - "IPAddress": "10.0.0.25", - "Port": 80, - "Type": "HTTP", - "ResourcePath": "/", - "FullyQualifiedDomainName": "example.com", - "SearchString": "a good response", - "RequestInterval": 10, - "FailureThreshold": 2, - }, - )["HealthCheck"] - - conn.delete_health_check(HealthCheckId=check["Id"]) - - checks = conn.list_health_checks()["HealthChecks"] - checks.should.have.length_of(0) - - @mock_route53 def test_use_health_check_in_resource_record_set(): conn = boto3.client("route53", region_name="us-east-1") @@ -457,70 +387,6 @@ def test_deleting_latency_route(): cnames[1]["Region"].should.equal("us-west-1") -@mock_ec2 -@mock_route53 -def test_hosted_zone_private_zone_preserved(): - # Create mock VPC so we can get a VPC ID - region = "us-east-1" - ec2c = boto3.client("ec2", region_name=region) - vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16").get("Vpc").get("VpcId") - - # Create hosted_zone as a Private VPC Hosted Zone - conn = boto3.client("route53", region_name=region) - new_zone = conn.create_hosted_zone( - Name="testdns.aws.com.", - CallerReference=str(hash("foo")), - HostedZoneConfig=dict(PrivateZone=True, Comment="Test"), - VPC={"VPCRegion": region, "VPCId": vpc_id}, - ) - - zone_id = new_zone["HostedZone"]["Id"].split("/")[-1] - hosted_zone = conn.get_hosted_zone(Id=zone_id) - hosted_zone["HostedZone"]["Config"]["PrivateZone"].should.equal(True) - hosted_zone.should.have.key("VPCs") - hosted_zone["VPCs"].should.have.length_of(1) - hosted_zone["VPCs"][0].should.have.key("VPCId") - hosted_zone["VPCs"][0].should.have.key("VPCRegion") - hosted_zone["VPCs"][0]["VPCId"].should_not.equal(None) - hosted_zone["VPCs"][0]["VPCRegion"].should_not.equal(None) - hosted_zone["VPCs"][0]["VPCId"].should.be.equal(vpc_id) - hosted_zone["VPCs"][0]["VPCRegion"].should.be.equal(region) - - hosted_zones = conn.list_hosted_zones() - hosted_zones["HostedZones"][0]["Config"]["PrivateZone"].should.equal(True) - - hosted_zones = conn.list_hosted_zones_by_name(DNSName="testdns.aws.com.") - hosted_zones["HostedZones"].should.have.length_of(1) - hosted_zones["HostedZones"][0]["Config"]["PrivateZone"].should.equal(True) - - # create_hosted_zone statements with PrivateZone=True, - # but without a _valid_ vpc-id should NOT fail. - zone2_name = "testdns2.aws.com." - no_vpc_zone = conn.create_hosted_zone( - Name=zone2_name, - CallerReference=str(hash("foo")), - HostedZoneConfig=dict(PrivateZone=True, Comment="Test without VPC"), - ) - - zone_id = no_vpc_zone["HostedZone"]["Id"].split("/")[-1] - hosted_zone = conn.get_hosted_zone(Id=zone_id) - hosted_zone["HostedZone"]["Config"]["PrivateZone"].should.equal(True) - hosted_zone.should.have.key("VPCs") - hosted_zone["VPCs"].should.have.length_of(0) - - hosted_zones = conn.list_hosted_zones() - hosted_zones["HostedZones"].should.have.length_of(2) - hosted_zones["HostedZones"][0]["Config"]["PrivateZone"].should.equal(True) - hosted_zones["HostedZones"][1]["Config"]["PrivateZone"].should.equal(True) - - hosted_zones = conn.list_hosted_zones_by_name(DNSName=zone2_name) - hosted_zones["HostedZones"].should.have.length_of(1) - hosted_zones["HostedZones"][0]["Config"]["PrivateZone"].should.equal(True) - hosted_zones["HostedZones"][0]["Name"].should.equal(zone2_name) - - return - - @mock_route53 def test_list_or_change_tags_for_resource_request(): conn = boto3.client("route53", region_name="us-east-1") @@ -737,69 +603,6 @@ def test_list_hosted_zones_by_dns_name(): zones["HostedZones"][3]["Name"].should.equal("test.a.org.") -@mock_ec2 -@mock_route53 -def test_list_hosted_zones_by_vpc(): - # Create mock VPC so we can get a VPC ID - ec2c = boto3.client("ec2", region_name="us-east-1") - vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16").get("Vpc").get("VpcId") - region = "us-east-1" - - conn = boto3.client("route53", region_name=region) - zone_b = conn.create_hosted_zone( - Name="test.b.com.", - CallerReference=str(hash("foo")), - HostedZoneConfig=dict(PrivateZone=True, Comment="test com"), - VPC={"VPCRegion": region, "VPCId": vpc_id}, - ) - zone_id = zone_b["HostedZone"]["Id"].split("/")[2] - response = conn.list_hosted_zones_by_vpc(VPCId=vpc_id, VPCRegion=region) - response.should.have.key("ResponseMetadata") - response.should.have.key("HostedZoneSummaries") - response["HostedZoneSummaries"].should.have.length_of(1) - response["HostedZoneSummaries"][0].should.have.key("HostedZoneId") - retured_zone = response["HostedZoneSummaries"][0] - retured_zone["HostedZoneId"].should.equal(zone_id) - retured_zone["Name"].should.equal(zone_b["HostedZone"]["Name"]) - - -@mock_ec2 -@mock_route53 -def test_list_hosted_zones_by_vpc_with_multiple_vpcs(): - # Create mock VPC so we can get a VPC ID - ec2c = boto3.client("ec2", region_name="us-east-1") - vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16").get("Vpc").get("VpcId") - region = "us-east-1" - - # Create 3 Zones associate with the VPC. - zones = {} - conn = boto3.client("route53", region_name=region) - for zone in ["a", "b", "c"]: - zone_name = f"test.{zone}.com." - zones[zone] = conn.create_hosted_zone( - Name=zone_name, - CallerReference=str(hash("foo")), - HostedZoneConfig=dict(PrivateZone=True, Comment=f"test {zone} com"), - VPC={"VPCRegion": region, "VPCId": vpc_id}, - ) - - # List the zones associated with this vpc - response = conn.list_hosted_zones_by_vpc(VPCId=vpc_id, VPCRegion=region) - response.should.have.key("ResponseMetadata") - response.should.have.key("HostedZoneSummaries") - response["HostedZoneSummaries"].should.have.length_of(3) - - # Loop through all zone summaries and verify they match what was created - for summary in response["HostedZoneSummaries"]: - # use the zone name as the index - index = summary["Name"].split(".")[1] - zone_id = zones[index]["HostedZone"]["Id"].split("/")[2] - summary.should.have.key("HostedZoneId") - summary["HostedZoneId"].should.equal(zone_id) - summary.should.have.key("Name") - summary["Name"].should.equal(zones[index]["HostedZone"]["Name"]) - - @mock_route53 def test_change_resource_record_sets_crud_valid(): conn = boto3.client("route53", region_name="us-east-1") @@ -1568,3 +1371,14 @@ def test_list_resource_recordset_pagination(): response.should.have.key("MaxItems").equals("300") response.shouldnt.have.key("NextRecordName") response.shouldnt.have.key("NextRecordType") + + +@mock_route53 +def test_get_dns_sec(): + client = boto3.client("route53", region_name="us-east-1") + + hosted_zone_id = client.create_hosted_zone( + Name="testdns.aws.com.", CallerReference=str(hash("foo")) + )["HostedZone"]["Id"] + dns_sec = client.get_dnssec(HostedZoneId=hosted_zone_id) + dns_sec.should.have.key("Status").equals({"ServeSignature": "NOT_SIGNING"}) diff --git a/tests/test_route53/test_route53_boto3.py b/tests/test_route53/test_route53_healthchecks.py similarity index 69% rename from tests/test_route53/test_route53_boto3.py rename to tests/test_route53/test_route53_healthchecks.py index 5db1618d1..c6e3f6cde 100644 --- a/tests/test_route53/test_route53_boto3.py +++ b/tests/test_route53/test_route53_healthchecks.py @@ -59,6 +59,7 @@ def test_create_health_check_with_additional_options(): "Type": "HTTP", "ResourcePath": "/", "FullyQualifiedDomainName": "example.com", + "SearchString": "a good response", "RequestInterval": 10, "FailureThreshold": 2, "MeasureLatency": True, @@ -73,9 +74,11 @@ def test_create_health_check_with_additional_options(): check.should.have.key("CallerReference").being.equal( "test-route53-health-HealthCheck-asdf" ) + check.should.have.key("HealthCheckVersion").equal(1) check.should.have.key("HealthCheckConfig") # config = check["HealthCheckConfig"] + check["HealthCheckConfig"].should.have.key("SearchString").equal("a good response") config.should.have.key("MeasureLatency").being.equal(True) config.should.have.key("Inverted").being.equal(True) config.should.have.key("Disabled").being.equal(True) @@ -203,11 +206,93 @@ def test_get_unknown_health_check(): @mock_route53 -def test_get_dns_sec(): +def test_list_health_checks(): + conn = boto3.client("route53", region_name="us-east-1") + + conn.list_health_checks()["HealthChecks"].should.have.length_of(0) + + check = conn.create_health_check( + CallerReference="?", + HealthCheckConfig={ + "IPAddress": "10.0.0.25", + "Port": 80, + "Type": "HTTP", + "ResourcePath": "/", + "FullyQualifiedDomainName": "example.com", + "SearchString": "a good response", + "RequestInterval": 10, + "FailureThreshold": 2, + }, + )["HealthCheck"] + + checks = conn.list_health_checks()["HealthChecks"] + checks.should.have.length_of(1) + checks.should.contain(check) + + +@mock_route53 +def test_delete_health_checks(): + conn = boto3.client("route53", region_name="us-east-1") + + conn.list_health_checks()["HealthChecks"].should.have.length_of(0) + + check = conn.create_health_check( + CallerReference="?", + HealthCheckConfig={ + "IPAddress": "10.0.0.25", + "Port": 80, + "Type": "HTTP", + "ResourcePath": "/", + "FullyQualifiedDomainName": "example.com", + "SearchString": "a good response", + "RequestInterval": 10, + "FailureThreshold": 2, + }, + )["HealthCheck"] + + conn.delete_health_check(HealthCheckId=check["Id"]) + + checks = conn.list_health_checks()["HealthChecks"] + checks.should.have.length_of(0) + + +@mock_route53 +def test_update_health_check(): client = boto3.client("route53", region_name="us-east-1") - hosted_zone_id = client.create_hosted_zone( - Name="testdns.aws.com.", CallerReference=str(hash("foo")) - )["HostedZone"]["Id"] - dns_sec = client.get_dnssec(HostedZoneId=hosted_zone_id) - dns_sec.should.have.key("Status").equals({"ServeSignature": "NOT_SIGNING"}) + hc_id = client.create_health_check( + CallerReference="callref", + HealthCheckConfig={ + "Type": "CALCULATED", + "Inverted": False, + "Disabled": False, + "HealthThreshold": 1, + }, + )["HealthCheck"]["Id"] + + client.update_health_check( + HealthCheckId=hc_id, + IPAddress="0.0.0.0", + Port=80, + ResourcePath="rp", + FullyQualifiedDomainName="example.com", + SearchString="search", + FailureThreshold=123, + Inverted=False, + Disabled=False, + HealthThreshold=13, + ChildHealthChecks=["child"], + Regions=["us-east-1", "us-east-2", "us-west-1"], + ) + + config = client.get_health_check(HealthCheckId=hc_id)["HealthCheck"][ + "HealthCheckConfig" + ] + config.should.have.key("Type").equals("CALCULATED") + config.should.have.key("ResourcePath").equals("rp") + config.should.have.key("FullyQualifiedDomainName").equals("example.com") + config.should.have.key("SearchString").equals("search") + config.should.have.key("Inverted").equals(False) + config.should.have.key("Disabled").equals(False) + config.should.have.key("ChildHealthChecks").equals(["child"]) + config.should.have.key("Regions").equals(["us-east-1", "us-east-2", "us-west-1"]) diff --git a/tests/test_route53/test_route53_vpcs.py b/tests/test_route53/test_route53_vpcs.py new file mode 100644 index 000000000..e892888c4 --- /dev/null +++ b/tests/test_route53/test_route53_vpcs.py @@ -0,0 +1,236 @@ +import boto3 +import sure # noqa # pylint: disable=unused-import +import pytest + +from botocore.exceptions import ClientError +from moto import mock_ec2, mock_route53 + + +@mock_ec2 +@mock_route53 +def test_hosted_zone_private_zone_preserved(): + # Create mock VPC so we can get a VPC ID + region = "us-east-1" + ec2c = boto3.client("ec2", region_name=region) + vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16").get("Vpc").get("VpcId") + + # Create hosted_zone as a Private VPC Hosted Zone + conn = boto3.client("route53", region_name=region) + new_zone = conn.create_hosted_zone( + Name="testdns.aws.com.", + CallerReference=str(hash("foo")), + HostedZoneConfig=dict(PrivateZone=True, Comment="Test"), + VPC={"VPCRegion": region, "VPCId": vpc_id}, + ) + + zone_id = new_zone["HostedZone"]["Id"].split("/")[-1] + hosted_zone = conn.get_hosted_zone(Id=zone_id) + hosted_zone["HostedZone"]["Config"]["PrivateZone"].should.equal(True) + hosted_zone.should.have.key("VPCs") + hosted_zone["VPCs"].should.have.length_of(1) + hosted_zone["VPCs"][0].should.have.key("VPCId") + hosted_zone["VPCs"][0].should.have.key("VPCRegion") + hosted_zone["VPCs"][0]["VPCId"].should.be.equal(vpc_id) + hosted_zone["VPCs"][0]["VPCRegion"].should.be.equal(region) + + hosted_zones = conn.list_hosted_zones() + hosted_zones["HostedZones"][0]["Config"]["PrivateZone"].should.equal(True) + + hosted_zones = conn.list_hosted_zones_by_name(DNSName="testdns.aws.com.") + hosted_zones["HostedZones"].should.have.length_of(1) + hosted_zones["HostedZones"][0]["Config"]["PrivateZone"].should.equal(True) + + # create_hosted_zone statements with PrivateZone=True, + # but without a _valid_ vpc-id should NOT fail. + zone2_name = "testdns2.aws.com." + no_vpc_zone = conn.create_hosted_zone( + Name=zone2_name, + CallerReference=str(hash("foo")), + HostedZoneConfig=dict(PrivateZone=True, Comment="Test without VPC"), + ) + + zone_id = no_vpc_zone["HostedZone"]["Id"].split("/")[-1] + hosted_zone = conn.get_hosted_zone(Id=zone_id) + hosted_zone["HostedZone"]["Config"]["PrivateZone"].should.equal(True) + hosted_zone.should.have.key("VPCs") + hosted_zone["VPCs"].should.have.length_of(0) + + hosted_zones = conn.list_hosted_zones() + hosted_zones["HostedZones"].should.have.length_of(2) + hosted_zones["HostedZones"][0]["Config"]["PrivateZone"].should.equal(True) + hosted_zones["HostedZones"][1]["Config"]["PrivateZone"].should.equal(True) + + hosted_zones = conn.list_hosted_zones_by_name(DNSName=zone2_name) + hosted_zones["HostedZones"].should.have.length_of(1) + hosted_zones["HostedZones"][0]["Config"]["PrivateZone"].should.equal(True) + hosted_zones["HostedZones"][0]["Name"].should.equal(zone2_name) + + +@mock_ec2 +@mock_route53 +def test_list_hosted_zones_by_vpc_with_multiple_vpcs(): + # Create mock VPC so we can get a VPC ID + ec2c = boto3.client("ec2", region_name="us-east-1") + vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16").get("Vpc").get("VpcId") + region = "us-east-1" + + # Create 3 Zones associate with the VPC. + zones = {} + conn = boto3.client("route53", region_name=region) + for zone in ["a", "b", "c"]: + zone_name = f"test.{zone}.com." + zones[zone] = conn.create_hosted_zone( + Name=zone_name, + CallerReference=str(hash("foo")), + HostedZoneConfig=dict(PrivateZone=True, Comment=f"test {zone} com"), + VPC={"VPCRegion": region, "VPCId": vpc_id}, + ) + + # List the zones associated with this vpc + response = conn.list_hosted_zones_by_vpc(VPCId=vpc_id, VPCRegion=region) + response.should.have.key("ResponseMetadata") + response.should.have.key("HostedZoneSummaries") + response["HostedZoneSummaries"].should.have.length_of(3) + + # Loop through all zone summaries and verify they match what was created + for summary in response["HostedZoneSummaries"]: + # use the zone name as the index + index = summary["Name"].split(".")[1] + zone_id = zones[index]["HostedZone"]["Id"].split("/")[2] + summary.should.have.key("HostedZoneId") + summary["HostedZoneId"].should.equal(zone_id) + summary.should.have.key("Name") + summary["Name"].should.equal(zones[index]["HostedZone"]["Name"]) + + +@mock_ec2 +@mock_route53 +def test_list_hosted_zones_by_vpc(): + # Create mock VPC so we can get a VPC ID + ec2c = boto3.client("ec2", region_name="us-east-1") + vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16").get("Vpc").get("VpcId") + region = "us-east-1" + + conn = boto3.client("route53", region_name=region) + zone_b = conn.create_hosted_zone( + Name="test.b.com.", + CallerReference=str(hash("foo")), + HostedZoneConfig=dict(PrivateZone=True, Comment="test com"), + VPC={"VPCRegion": region, "VPCId": vpc_id}, + ) + zone_id = zone_b["HostedZone"]["Id"].split("/")[2] + + response = conn.list_hosted_zones_by_vpc(VPCId=vpc_id, VPCRegion=region) + response.should.have.key("ResponseMetadata") + response.should.have.key("HostedZoneSummaries") + response["HostedZoneSummaries"].should.have.length_of(1) + response["HostedZoneSummaries"][0].should.have.key("HostedZoneId") + retured_zone = response["HostedZoneSummaries"][0] + retured_zone["HostedZoneId"].should.equal(zone_id) + retured_zone["Name"].should.equal(zone_b["HostedZone"]["Name"]) + + +@mock_ec2 +@mock_route53 +def test_route53_associate_vpc(): + ec2c = boto3.client("ec2", region_name="us-east-1") + vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16")["Vpc"]["VpcId"] + conn = boto3.client("route53", region_name="us-east-1") + zone = conn.create_hosted_zone( + Name="test.b.com.", + CallerReference=str(hash("foo")), + HostedZoneConfig=dict(PrivateZone=True, Comment=""), + ) + zone_id = zone["HostedZone"]["Id"].split("/")[2] + + resp = conn.associate_vpc_with_hosted_zone( + HostedZoneId=zone_id, + VPC={"VPCId": vpc_id, "VPCRegion": "us-east-1"}, + Comment="yolo", + ) + resp.should.have.key("ChangeInfo") + resp["ChangeInfo"].should.have.key("Comment").equals("yolo") + + +@mock_ec2 +@mock_route53 +def test_route53_associate_vpc_with_public_Zone(): + ec2c = boto3.client("ec2", region_name="us-east-1") + vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16")["Vpc"]["VpcId"] + conn = boto3.client("route53", region_name="us-east-1") + zone = conn.create_hosted_zone( + Name="test.b.com.", + CallerReference=str(hash("foo")), + ) + zone_id = zone["HostedZone"]["Id"].split("/")[2] + + with pytest.raises(ClientError) as exc: + conn.associate_vpc_with_hosted_zone( + HostedZoneId=zone_id, + VPC={"VPCId": vpc_id, "VPCRegion": "us-east-1"}, + Comment="yolo", + ) + err = exc.value.response["Error"] + err["Code"].should.equal("PublicZoneVPCAssociation") + err["Message"].should.equal( + "You're trying to associate a VPC with a public hosted zone. Amazon Route 53 doesn't support associating a VPC with a public hosted zone." + ) + + +@mock_ec2 +@mock_route53 +def test_route53_associate_and_disassociate_vpc(): + ec2c = boto3.client("ec2", region_name="us-east-1") + vpc_id1 = ec2c.create_vpc(CidrBlock="10.1.0.0/16").get("Vpc").get("VpcId") + vpc_id2 = ec2c.create_vpc(CidrBlock="10.1.0.1/16").get("Vpc").get("VpcId") + region = "us-east-1" + + conn = boto3.client("route53", region_name=region) + zone = conn.create_hosted_zone( + Name="test.b.com.", + CallerReference=str(hash("foo")), + HostedZoneConfig=dict(PrivateZone=True, Comment="test com"), + VPC={"VPCRegion": region, "VPCId": vpc_id1}, + ) + zone_id = zone["HostedZone"]["Id"].split("/")[2] + + conn.associate_vpc_with_hosted_zone( + HostedZoneId=zone_id, + VPC={"VPCId": vpc_id2, "VPCRegion": region}, + ) + + zone_vpcs = conn.get_hosted_zone(Id=zone_id)["VPCs"] + zone_vpcs.should.have.length_of(2) + zone_vpcs.should.contain({"VPCRegion": region, "VPCId": vpc_id1}) + zone_vpcs.should.contain({"VPCRegion": region, "VPCId": vpc_id2}) + + conn.disassociate_vpc_from_hosted_zone(HostedZoneId=zone_id, VPC={"VPCId": vpc_id1}) + + zone_vpcs = conn.get_hosted_zone(Id=zone_id)["VPCs"] + zone_vpcs.should.have.length_of(1) + zone_vpcs.should.contain({"VPCRegion": region, "VPCId": vpc_id2}) + + +@mock_ec2 +@mock_route53 +def test_route53_disassociate_last_vpc(): + ec2c = boto3.client("ec2", region_name="us-east-1") + vpc_id = ec2c.create_vpc(CidrBlock="10.1.0.0/16")["Vpc"]["VpcId"] + conn = boto3.client("route53", region_name="us-east-1") + zone = conn.create_hosted_zone( + Name="test.b.com.", + CallerReference=str(hash("foo")), + HostedZoneConfig=dict(PrivateZone=True, Comment="test com"), + VPC={"VPCRegion": "us-east-1", "VPCId": vpc_id}, + ) + zone_id = zone["HostedZone"]["Id"].split("/")[2] + + with pytest.raises(ClientError) as exc: + conn.disassociate_vpc_from_hosted_zone( + HostedZoneId=zone_id, VPC={"VPCId": vpc_id} + ) + err = exc.value.response["Error"] + err["Code"].should.equal("LastVPCAssociation") + err["Message"].should.equal( + "The VPC that you're trying to disassociate from the private hosted zone is the last VPC that is associated with the hosted zone. Amazon Route 53 doesn't support disassociating the last VPC from a hosted zone." + )