Repository: octodns/octodns-route53

Issue:
Public and Private DNS in Route53
Am I missing step somewhere? How to distinguish between the public and private hosted zone types in Route53?

We have a single domain for both public and private, the data within is obviosuly very different. We'd like to migrate away from RoadWorker and possible use OctoDNS. When I run octodns-dump it only grabs the private data, not the public. How do you grab both, and ultimately manage and sync both from separate config files?

Thanks

Discussion:
@ross: Not familiar with a setup that uses a single zone for both internal and external records and octoDNS doesn't have any Route53 code specific to handling that sort of set up. We manage dozens of internal zones and dozens of external zones, but nothing that's both. @yzguy: octodns/octodns#260 and octodns/octodns#190 are essentially the same thing. It's somewhat trivial to add it just looking at public hosted zones or private hosted zones. The tougher part is how to keep them in config/dump because with a very basic configuration one would overwrite the other.

I believe your earlier response: https://github.com/github/octodns/issues/260#issuecomment-417824820 is probably the way to go

You would have providers configured like 

```yaml
  route53:
    class: octodns.provider.route53.Route53Provider
    access_key_id: env/AWS_ACCESS_KEY_ID
    secret_access_key: env/AWS_SECRET_ACCESS_KEY
```

```yaml
  route53_private:
    class: octodns.provider.route53.Route53Provider
    access_key_id: env/AWS_ACCESS_KEY_ID
    secret_access_key: env/AWS_SECRET_ACCESS_KEY
    private_zones: true
```

Then you can use them like normal.

I have a draft PR up for the small change but need to work on coverage and hear any feedback: https://github.com/github/octodns/pull/421

This would be somewhat of an impactful change for people who use OctoDNS for private hosted zone management already, as they would need to go add the `private_zones: true` to their provider.@unknown: Thanks, I use that info above to make it work. Honestly, I just need to get the data out of Route53 via octodns-dump, we're migrating to a new provider and in the future the public/private wont be over the same domain again. The dump saves me time since we have so many records.@github-actions: This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 7 days.@ross: From: https://github.com/octodns/octodns/issues/728#issuecomment-2211292530

> is there any opened issue for this in the new repo.

Doesn't look like it. I guess it stalled out and was forgotten about. My gut says that the VPC-ID would be better tied to provider rather than per zone, that way you could do things like:

```yaml
providers:
  vpc-1234:
    ...

  vpc-2345:
    ....

zones:
  something.in.both.:
    sources:
      ..
    targets:
      - vpc-1234
      - vpc-2345
```

Don't off-hand know if that'll run into any problems. Working on `Route53Provider` is annoying as it costs me $ out of my own pocket, so if you have a TAM or any connections there i'd be nice if they could do something like a lot of the other providers and provide some sort of test account or credits.@ross: Reopening this while I'm investigating things, but this is looking really complicated. My above thinking of doing `vpc_id` isn't really viable even if it was switched to an array `vpcs.region/id`. Problem is that if the vpc associations are changed there's nothing saying which one in the config matches which in Route53 since they're many-to-many. Same is true if the metadata is associated with the Zone as @yzguy originally suggested, just in the opposite direction. It would require some sort of manual association or external state to record the mappings. 

Going to poke around a bit more, but not seeing an obvious path and the non-obvious ones are enough work that it's not going to happen short of someone interested in paying for the research & development required. @ross: OK. So I think this is doable, but as mentioned above more involved than I have the time to devote to it as free/OSS work atm. Mostly the API for managing this stuff is multi-step and thus requires lots of edge case and error handling as well as the complications involved in keeping the associated VPCs in sync. 

Going to leave this here & open atm. The bot will auto-close if it ends up sitting.

/cc @JohanLeirnes fyi@rasturic: Would leveraging hosted zone tag matching be a viable option?  

Or possibly hosted zone ID, but then in restricted environments like govcloud, we would not be able to have the hosted zone ID in configurations residing outside of the boundary.@ross: The state would end up needing to live somewhere, object tags and/or records, e.g. TXT values are possibilities. Without doing the research and some prototyping it's hard to say what would fully work.@rasturic: Yes.  And I  imagine having per-zone provider environment variables would be a major feature.  Not sure what the value might be outside of Route53 though.@github-actions: This issue is stale because it has been open 90 days with no activity. Remove stale label or comment or this will be closed in 7 days.@jmreicha: I started looking at how to implement this, but not sure what the design should look like. My first pass was to make an optional key in the zones yaml configuration, zone_id as a starting point. Would that be a reasonable starting point? Not sure about this piece,

> The state would end up needing to live somewhere, object tags and/or records, e.g. TXT values are possibilities.
@ross: > The state would end up needing to live somewhere, object tags and/or records, e.g. TXT values are possibilities.

Without paging it all back into my working memory I believe that comment was in reference to managing all the links between the private zone and the associated VPCs. This comment has a bit more info https://github.com/octodns/octodns-route53/issues/12#issuecomment-2220988752. This is the part that lead me to decide that it was the sort of thing that would require sitting down and working on as an actual project. It would also cost a decent chunk of $ to set up all the VPCs and resources involved in designing, developing, and testing it out.

Solution patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 5ad1e79..433b511 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,10 @@
+## v1.?.? - 2025-??-?? - ???
+
+* Multiple zones with the same name will now throw an error message, behavior
+  previously would not have been deterministic.
+* New provider paramater, private, added to enable specifying zone type. Note
+  that VPC associations are managed.
+
 ## v1.0.1 - 2025-05-05 - Only clamp when forced
 
 * Don't clamp urllib3 unless we're on 3.8 or 3.9 where it's actually needed
diff --git a/README.md b/README.md
index b9807f0..ed438ee 100644
--- a/README.md
+++ b/README.md
@@ -47,6 +47,10 @@ providers:
     #session_token: env/AWS_SESSION_TOKEN
     # The AWS profile name (optional)
     #profile:
+    # Optionally restrict hosted zone lookup to only private or public zones.
+    # If zone creation is required and this option is set, zones will be created as private.
+    # Set to true to only use private zones, false for public zones, or omit for no restriction.
+    #private: False
 ```
 
 Alternatively, you may leave out access_key_id, secret_access_key and session_token.  This will result in boto3 deciding authentication dynamically.
diff --git a/octodns_route53/provider.py b/octodns_route53/provider.py
index 28e4038..9aebb43 100644
--- a/octodns_route53/provider.py
+++ b/octodns_route53/provider.py
@@ -677,12 +677,14 @@ class Route53Provider(_AuthMixin, BaseProvider):
         profile=None,
         delegation_set_id=None,
         get_zones_by_name=False,
+        private=None,
         *args,
         **kwargs,
     ):
         self.max_changes = max_changes
         self.delegation_set_id = delegation_set_id
         self.get_zones_by_name = get_zones_by_name
+        self.private = private
 
         self.log = logging.getLogger(f'Route53Provider[{id}]')
         self.log.info(
@@ -711,14 +713,24 @@ class Route53Provider(_AuthMixin, BaseProvider):
 
     def _get_zone_id_by_name(self, name):
         # attempt to get zone by name
-        # limited to one as this should be unique
+        resp = self._conn.list_hosted_zones_by_name(
+            DNSName=name, MaxItems="100"
+        )
         id = None
-        resp = self._conn.list_hosted_zones_by_name(DNSName=name, MaxItems="1")
         if len(resp['HostedZones']) != 0:
-            # if there is a response that starts with the name
-            if _octal_replace(resp['HostedZones'][0]['Name']).startswith(name):
-                id = resp['HostedZones'][0]['Id']
-                self.log.debug('get_zones_by_name:   id=%s', id)
+            for z in resp['HostedZones']:
+                private_zone = z.get('Config', {}).get('PrivateZone', False)
+                if self.private is not None and self.private != private_zone:
+                    continue
+
+                # if there is a response that starts with the name
+                if _octal_replace(z['Name']).startswith(name):
+                    if id is not None:
+                        raise Route53ProviderException(
+                            f'Multiple zones named "{z["Name"]}" were found.'
+                        )
+                    id = z['Id']
+                    self.log.debug('get_zones_by_name:   id=%s', id)
         return id
 
     def update_r53_zones(self, name):
@@ -736,7 +748,20 @@ class Route53Provider(_AuthMixin, BaseProvider):
                 while more:
                     resp = self._conn.list_hosted_zones(**start)
                     for z in resp['HostedZones']:
-                        zones[_octal_replace(z['Name'])] = z['Id']
+                        private_zone = z.get('Config', {}).get(
+                            'PrivateZone', False
+                        )
+                        if (
+                            self.private is not None
+                            and self.private != private_zone
+                        ):
+                            continue
+                        zname = _octal_replace(z['Name'])
+                        if zname in zones:
+                            raise Route53ProviderException(
+                                f'Multiple zones named "{zname}" were found.'
+                            )
+                        zones[zname] = z['Id']
                     more = resp['IsTruncated']
                     start['Marker'] = resp.get('NextMarker', None)
                 self._r53_zones = zones
@@ -758,14 +783,12 @@ class Route53Provider(_AuthMixin, BaseProvider):
             self.log.debug(
                 '_get_zone_id:   no matching zone, creating, ref=%s', ref
             )
+            params = {"Name": name, "CallerReference": ref}
             if del_set:
-                resp = self._conn.create_hosted_zone(
-                    Name=name, CallerReference=ref, DelegationSetId=del_set
-                )
-            else:
-                resp = self._conn.create_hosted_zone(
-                    Name=name, CallerReference=ref
-                )
+                params["DelegationSetId"] = del_set
+            if self.private is not None:
+                params["HostedZoneConfig"] = {"PrivateZone": self.private}
+            resp = self._conn.create_hosted_zone(**params)
             self._r53_zones[name] = id = resp['HostedZone']['Id']
         return id
 
@@ -1105,7 +1128,15 @@ class Route53Provider(_AuthMixin, BaseProvider):
         more = True
         while more:
             resp = self._conn.list_hosted_zones(**params)
-            hosted_zones.extend([h['Name'] for h in resp['HostedZones']])
+            for h in resp['HostedZones']:
+                private_zone = h.get('Config', {}).get('PrivateZone', False)
+                if self.private is not None and self.private != private_zone:
+                    continue
+                if h['Name'] in hosted_zones:
+                    raise Route53ProviderException(
+                        f'Multiple zones named "{h["Name"]}" were found.'
+                    )
+                hosted_zones.append(h['Name'])
             params['Marker'] = resp.get('NextMarker', None)
             more = resp['IsTruncated']
 


Test patch:
diff --git a/tests/test_octodns_provider_route53.py b/tests/test_octodns_provider_route53.py
index 76919b4..fc9905b 100644
--- a/tests/test_octodns_provider_route53.py
+++ b/tests/test_octodns_provider_route53.py
@@ -608,6 +608,17 @@ class TestRoute53Provider(TestCase):
 
         return (provider, stubber)
 
+    def _get_stubbed_private_provider(self):
+        provider = Route53Provider(
+            'test', 'abc', '123', strict_supports=False, private=True
+        )
+
+        # Use the stubber
+        stubber = Stubber(provider._conn)
+        stubber.activate()
+
+        return (provider, stubber)
+
     def _get_stubbed_fallback_auth_provider(self):
         provider = Route53Provider('test', strict_supports=False)
 
@@ -628,6 +639,22 @@ class TestRoute53Provider(TestCase):
 
         return (provider, stubber)
 
+    def _get_stubbed_get_zones_by_name_enabled_private_provider(self):
+        provider = Route53Provider(
+            'test',
+            'abc',
+            '123',
+            get_zones_by_name=True,
+            strict_supports=False,
+            private=True,
+        )
+
+        # Use the stubber
+        stubber = Stubber(provider._conn)
+        stubber.activate()
+
+        return (provider, stubber)
+
     def test_update_r53_zones(self):
         provider, stubber = self._get_stubbed_provider()
 
@@ -651,6 +678,102 @@ class TestRoute53Provider(TestCase):
         provider.update_r53_zones("unit.tests.")
         self.assertEqual(provider._r53_zones, {'unit.tests.': 'z40'})
 
+    def test_update_r53_zones_private(self):
+        provider, stubber = self._get_stubbed_private_provider()
+
+        list_hosted_zones = {
+            'HostedZones': [
+                {
+                    'Id': 'z40',
+                    'Name': 'unit.tests.',
+                    'CallerReference': 'abc',
+                    'Config': {'Comment': 'string', 'PrivateZone': False},
+                    'ResourceRecordSetCount': 123,
+                },
+                {
+                    'Id': 'z41',
+                    'Name': 'unit.tests.',
+                    'CallerReference': 'abc',
+                    'Config': {'Comment': 'string', 'PrivateZone': True},
+                    'ResourceRecordSetCount': 123,
+                },
+            ],
+            'Marker': 'm',
+            'IsTruncated': False,
+            'MaxItems': '100',
+        }
+
+        stubber.add_response('list_hosted_zones', list_hosted_zones)
+
+        provider.update_r53_zones("unit.tests.")
+        self.assertEqual(provider._r53_zones, {'unit.tests.': 'z41'})
+
+    def test_update_r53_zones_multiple(self):
+        provider, stubber = self._get_stubbed_provider()
+
+        list_hosted_zones = {
+            'HostedZones': [
+                {
+                    'Id': 'z40',
+                    'Name': 'unit.tests.',
+                    'CallerReference': 'abc',
+                    'Config': {'Comment': 'string', 'PrivateZone': False},
+                    'ResourceRecordSetCount': 123,
+                },
+                {
+                    'Id': 'z41',
+                    'Name': 'unit.tests.',
+                    'CallerReference': 'abc',
+                    'Config': {'Comment': 'string', 'PrivateZone': True},
+                    'ResourceRecordSetCount': 123,
+                },
+            ],
+            'Marker': 'm',
+            'IsTruncated': False,
+            'MaxItems': '100',
+        }
+
+        stubber.add_response('list_hosted_zones', list_hosted_zones)
+
+        with self.assertRaises(Route53ProviderException):
+            provider.update_r53_zones("unit.tests.")
+
+    def test_get_r53_private_zones_with_get_zones_by_name(self):
+        (provider, stubber) = (
+            self._get_stubbed_get_zones_by_name_enabled_private_provider()
+        )
+
+        list_hosted_zones_by_name_resp = {
+            'HostedZones': [
+                {
+                    'Id': 'z40',
+                    'Name': 'unit.tests.',
+                    'CallerReference': 'abc',
+                    'Config': {'Comment': 'string', 'PrivateZone': False},
+                    'ResourceRecordSetCount': 123,
+                },
+                {
+                    'Id': 'z41',
+                    'Name': 'unit.tests.',
+                    'CallerReference': 'abc',
+                    'Config': {'Comment': 'string', 'PrivateZone': True},
+                    'ResourceRecordSetCount': 123,
+                },
+            ],
+            'DNSName': 'unit.tests.',
+            'IsTruncated': False,
+            'MaxItems': '100',
+        }
+
+        stubber.add_response(
+            'list_hosted_zones_by_name',
+            list_hosted_zones_by_name_resp,
+            {'DNSName': 'unit.tests.', 'MaxItems': '100'},
+        )
+
+        provider.update_r53_zones("unit.tests.")
+        self.assertEqual(provider._r53_zones, {'unit.tests.': 'z41'})
+
     def test_update_r53_zones_with_get_zones_by_name(self):
         (provider, stubber) = (
             self._get_stubbed_get_zones_by_name_enabled_provider()
@@ -668,13 +791,13 @@ class TestRoute53Provider(TestCase):
             ],
             'DNSName': 'unit.tests.',
             'IsTruncated': False,
-            'MaxItems': '1',
+            'MaxItems': '100',
         }
 
         stubber.add_response(
             'list_hosted_zones_by_name',
             list_hosted_zones_by_name_resp,
-            {'DNSName': 'unit.tests.', 'MaxItems': '1'},
+            {'DNSName': 'unit.tests.', 'MaxItems': '100'},
         )
 
         provider.update_r53_zones("unit.tests.")
@@ -722,13 +845,13 @@ class TestRoute53Provider(TestCase):
             ],
             'DNSName': '0/25.2.0.192.in-addr.arpa.',
             'IsTruncated': False,
-            'MaxItems': '1',
+            'MaxItems': '100',
         }
 
         stubber.add_response(
             'list_hosted_zones_by_name',
             list_hosted_zones_by_name_resp,
-            {'DNSName': '0/25.2.0.192.in-addr.arpa.', 'MaxItems': '1'},
+            {'DNSName': '0/25.2.0.192.in-addr.arpa.', 'MaxItems': '100'},
         )
 
         provider.update_r53_zones("0/25.2.0.192.in-addr.arpa.")
@@ -995,6 +1118,49 @@ class TestRoute53Provider(TestCase):
         stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
         self.assertEqual(['alpha.com.', 'unit.tests.'], provider.list_zones())
 
+    def test_list_zones_multiple(self):
+        provider, stubber = self._get_stubbed_provider()
+
+        list_hosted_zones_mutliple_resp = {
+            'HostedZones': [
+                {'Name': 'unit.tests.', 'Id': 'z42', 'CallerReference': 'abc'},
+                {'Name': 'unit.tests.', 'Id': 'z43', 'CallerReference': 'abd'},
+            ],
+            'Marker': '',
+            'IsTruncated': False,
+            'MaxItems': '100',
+        }
+        stubber.add_response(
+            'list_hosted_zones', list_hosted_zones_mutliple_resp, {}
+        )
+        with self.assertRaises(Route53ProviderException):
+            provider.list_zones()
+
+    def test_list_private_zones(self):
+        provider, stubber = self._get_stubbed_private_provider()
+
+        list_hosted_zones_resp = {
+            'HostedZones': [
+                {
+                    'Name': 'unit.tests.',
+                    'Id': 'z42',
+                    'CallerReference': 'abc',
+                    'Config': {'PrivateZone': False},
+                },
+                {
+                    'Name': 'alpha.com.',
+                    'Id': 'z43',
+                    'CallerReference': 'abd',
+                    'Config': {'PrivateZone': True},
+                },
+            ],
+            'Marker': '',
+            'IsTruncated': False,
+            'MaxItems': '100',
+        }
+        stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
+        self.assertEqual(['alpha.com.'], provider.list_zones())
+
     def test_delegated_list_zones(self):
         provider, stubber = self._get_stubbed_delegation_set_provider()
 
@@ -1577,6 +1743,100 @@ class TestRoute53Provider(TestCase):
         self.assertEqual(13, provider.apply(plan))
         stubber.assert_no_pending_responses()
 
+    def test_sync_create_private(self):
+        provider, stubber = self._get_stubbed_private_provider()
+
+        got = Zone('unit.tests.', [])
+
+        list_hosted_zones_resp = {
+            'HostedZones': [],
+            'Marker': 'm',
+            'IsTruncated': False,
+            'MaxItems': '100',
+        }
+        stubber.add_response('list_hosted_zones', list_hosted_zones_resp, {})
+
+        plan = provider.plan(self.expected)
+        self.assertEqual(13, len(plan.changes))
+        self.assertFalse(plan.exists)
+        for change in plan.changes:
+            self.assertIsInstance(change, Create)
+        stubber.assert_no_pending_responses()
+
+        create_hosted_zone_resp = {
+            'HostedZone': {
+                'Name': 'unit.tests.',
+                'Id': 'z42',
+                'CallerReference': 'abc',
+                'Config': {'PrivateZone': True},
+            },
+            'ChangeInfo': {
+                'Id': 'a12',
+                'Status': 'PENDING',
+                'SubmittedAt': '2017-01-29T01:02:03Z',
+                'Comment': 'hrm',
+            },
+            'DelegationSet': {
+                'Id': 'b23',
+                'CallerReference': 'blip',
+                'NameServers': ['n12.unit.tests.'],
+            },
+            'Location': 'us-east-1',
+        }
+        stubber.add_response(
+            'create_hosted_zone',
+            create_hosted_zone_resp,
+            {
+                'Name': got.name,
+                'CallerReference': ANY,
+                'HostedZoneConfig': {'PrivateZone': True},
+            },
+        )
+
+        list_resource_record_sets_resp = {
+            'ResourceRecordSets': [
+                {
+                    'Name': 'a.unit.tests.',
+                    'Type': 'A',
+                    'GeoLocation': {'ContinentCode': 'NA'},
+                    'ResourceRecords': [{'Value': '2.2.3.4'}],
+                    'TTL': 61,
+                }
+            ],
+            'IsTruncated': False,
+            'MaxItems': '100',
+        }
+        stubber.add_response(
+            'list_resource_record_sets',
+            list_resource_record_sets_resp,
+            {'HostedZoneId': 'z42'},
+        )
+
+        stubber.add_response(
+            'list_health_checks',
+            {
+                'HealthChecks': self.health_checks,
+                'IsTruncated': False,
+                'MaxItems': '100',
+                'Marker': '',
+            },
+        )
+
+        stubber.add_response(
+            'change_resource_record_sets',
+            {
+                'ChangeInfo': {
+                    'Id': 'id',
+                    'Status': 'PENDING',
+                    'SubmittedAt': '2017-01-29T01:02:03Z',
+                }
+            },
+            {'HostedZoneId': 'z42', 'ChangeBatch': ANY},
+        )
+
+        self.assertEqual(13, provider.apply(plan))
+        stubber.assert_no_pending_responses()
+
     def test_health_checks_pagination(self):
         provider, stubber = self._get_stubbed_provider()
 
@@ -2408,7 +2668,7 @@ class TestRoute53Provider(TestCase):
         stubber.add_response(
             'list_hosted_zones_by_name',
             list_hosted_zones_by_name_resp_1,
-            {'DNSName': 'unit.tests.', 'MaxItems': '1'},
+            {'DNSName': 'unit.tests.', 'MaxItems': '100'},
         )
 
         # empty is empty
@@ -2420,7 +2680,7 @@ class TestRoute53Provider(TestCase):
         stubber.add_response(
             'list_hosted_zones_by_name',
             list_hosted_zones_by_name_resp_2,
-            {'DNSName': 'unit2.tests.', 'MaxItems': '1'},
+            {'DNSName': 'unit2.tests.', 'MaxItems': '100'},
         )
 
         # empty is empty
@@ -2453,7 +2713,7 @@ class TestRoute53Provider(TestCase):
         stubber.add_response(
             'list_hosted_zones_by_name',
             list_hosted_zones_by_name_resp,
-            {'DNSName': 'unit.tests.', 'MaxItems': '1'},
+            {'DNSName': 'unit.tests.', 'MaxItems': '100'},
         )
 
         # empty is empty
@@ -2462,6 +2722,62 @@ class TestRoute53Provider(TestCase):
         self.assertEqual([], extra)
         stubber.assert_no_pending_responses()
 
+    def test_get_zones_by_name_mutiple_zone_exists(self):
+        (provider, stubber) = (
+            self._get_stubbed_get_zones_by_name_enabled_provider()
+        )
+
+        list_hosted_zones_by_name_resp = {
+            'HostedZones': [
+                {
+                    'Id': 'z42',
+                    'Name': 'unit.tests.',
+                    'CallerReference': 'abc',
+                    'Config': {'Comment': 'string', 'PrivateZone': False},
+                    'ResourceRecordSetCount': 123,
+                },
+                {
+                    'Id': 'z43',
+                    'Name': 'unit.tests.',
+                    'CallerReference': 'abc',
+                    'Config': {'Comment': 'string', 'PrivateZone': True},
+                    'ResourceRecordSetCount': 123,
+                },
+            ],
+            'DNSName': 'unit.tests.',
+            'HostedZoneId': 'z42',
+            'IsTruncated': False,
+            'MaxItems': 'string',
+        }
+
+        list_resource_record_sets_resp = {
+            'ResourceRecordSets': [
+                {
+                    'Name': 'a.unit.tests.',
+                    'Type': 'A',
+                    'ResourceRecords': [{'Value': '2.2.3.4'}],
+                    'TTL': 61,
+                }
+            ],
+            'IsTruncated': False,
+            'MaxItems': '100',
+        }
+
+        stubber.add_response(
+            'list_hosted_zones_by_name',
+            list_hosted_zones_by_name_resp,
+            {'DNSName': 'unit.tests.', 'MaxItems': '100'},
+        )
+
+        stubber.add_response(
+            'list_resource_record_sets',
+            list_resource_record_sets_resp,
+            {'HostedZoneId': 'z42'},
+        )
+
+        with self.assertRaises(Route53ProviderException):
+            provider.plan(self.expected)
+
     def test_plan_apply_with_get_zones_by_name_zone_not_exists(self):
         (provider, stubber) = (
             self._get_stubbed_get_zones_by_name_enabled_provider()
@@ -2480,7 +2796,7 @@ class TestRoute53Provider(TestCase):
         stubber.add_response(
             'list_hosted_zones_by_name',
             list_hosted_zones_by_name_resp,
-            {'DNSName': 'unit.tests.', 'MaxItems': '1'},
+            {'DNSName': 'unit.tests.', 'MaxItems': '100'},
         )
 
         plan = provider.plan(self.expected)
@@ -2592,7 +2908,7 @@ class TestRoute53Provider(TestCase):
         stubber.add_response(
             'list_hosted_zones_by_name',
             list_hosted_zones_by_name_resp,
-            {'DNSName': 'unit.tests.', 'MaxItems': '1'},
+            {'DNSName': 'unit.tests.', 'MaxItems': '100'},
         )
 
         stubber.add_response(