From 9d7c48b96f1fb6da9c89cfa0336019f80b1bacd6 Mon Sep 17 00:00:00 2001 From: soniafrancisNS1 Date: Wed, 17 Dec 2025 16:10:49 +0000 Subject: [PATCH 1/4] Add zone export functionality - Add export() method to REST API and Zone class - Add unit test for zone export - Add simple usage example Allows customers to export zones in BIND format for backup/migration. --- examples/zone-export.py | 27 +++++++++++++++++++++++++++ ns1/rest/zones.py | 14 ++++++++++++++ ns1/zones.py | 12 ++++++++++++ tests/unit/test_zone.py | 14 ++++++++++++++ 4 files changed, 67 insertions(+) create mode 100644 examples/zone-export.py diff --git a/examples/zone-export.py b/examples/zone-export.py new file mode 100644 index 0000000..9bf5491 --- /dev/null +++ b/examples/zone-export.py @@ -0,0 +1,27 @@ +# +# Copyright (c) 2014 NSONE, Inc. +# +# License under The MIT License (MIT). See LICENSE in project root. +# + +from ns1 import NS1 + +# NS1 will use config in ~/.nsone by default +api = NS1() + +# to specify an apikey here instead, use: +# api = NS1(apiKey='<>') + +# to load an alternate configuration file: +# api = NS1(configFile='/etc/ns1/api.json') + +# export a zone to BIND format +zone = api.loadZone("example.com") +zone_file = zone.export() +print(zone_file) + +# save to a file +with open("example.com.zone", "w") as f: + f.write(zone_file) + +# Made with Bob diff --git a/ns1/rest/zones.py b/ns1/rest/zones.py index 07f01fe..37c774f 100644 --- a/ns1/rest/zones.py +++ b/ns1/rest/zones.py @@ -188,6 +188,20 @@ def delete_version(self, zone, version_id, callback=None, errback=None): errback=errback, ) + def export(self, zone, callback=None, errback=None): + """ + Export zone as BIND-compatible zone file. + + :param str zone: zone name + :return: zone file content as string + """ + return self._make_request( + "GET", + f"{self.ROOT}/{zone}/export", + callback=callback, + errback=errback, + ) + # successive pages just extend the list of zones def zone_list_pagination(curr_json, next_json): diff --git a/ns1/zones.py b/ns1/zones.py index 3e5a8d8..5bcf0c1 100644 --- a/ns1/zones.py +++ b/ns1/zones.py @@ -287,3 +287,15 @@ def usage(self, callback=None, errback=None, **kwargs): return stats.usage( zone=self.zone, callback=callback, errback=errback, **kwargs ) + + def export(self, callback=None, errback=None): + """ + Export zone as a BIND-compatible zone file. + + :param callback: optional callback + :param errback: optional error callback + :return: zone file content as string + """ + return self._rest.export( + self.zone, callback=callback, errback=errback + ) diff --git a/tests/unit/test_zone.py b/tests/unit/test_zone.py index 5dae71d..97b7014 100644 --- a/tests/unit/test_zone.py +++ b/tests/unit/test_zone.py @@ -251,3 +251,17 @@ def test_rest_zone_buildbody(zones_config): "tags": {"foo": "bar", "hai": "bai"}, } assert z._buildBody(zone, **kwargs) == body + + + +@pytest.mark.parametrize("zone, url", [("test.zone", "zones/test.zone/export")]) +def test_rest_zone_export(zones_config, zone, url): + z = ns1.rest.zones.Zones(zones_config) + z._make_request = mock.MagicMock() + z.export(zone) + z._make_request.assert_called_once_with( + "GET", + url, + callback=None, + errback=None, + ) From 12d4ad16e36e15e05edf9a9883c97d09bb808f10 Mon Sep 17 00:00:00 2001 From: soniafrancisNS1 Date: Wed, 17 Dec 2025 16:22:33 +0000 Subject: [PATCH 2/4] zone name --- examples/zone-export.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/examples/zone-export.py b/examples/zone-export.py index 9bf5491..c907964 100644 --- a/examples/zone-export.py +++ b/examples/zone-export.py @@ -1,5 +1,5 @@ # -# Copyright (c) 2014 NSONE, Inc. +# Copyright (c) 2025 NSONE, Inc. # # License under The MIT License (MIT). See LICENSE in project root. # @@ -24,4 +24,3 @@ with open("example.com.zone", "w") as f: f.write(zone_file) -# Made with Bob From 9d1500d199d8b4abc66abdbbc08fdd5e2de94bbb Mon Sep 17 00:00:00 2001 From: soniafrancisNS1 Date: Wed, 17 Dec 2025 16:29:23 +0000 Subject: [PATCH 3/4] ruff formatting --- examples/zone-export.py | 1 - ns1/rest/zones.py | 2 +- ns1/zones.py | 2 +- tests/unit/test_zone.py | 1 - 4 files changed, 2 insertions(+), 4 deletions(-) diff --git a/examples/zone-export.py b/examples/zone-export.py index c907964..3c2a2b5 100644 --- a/examples/zone-export.py +++ b/examples/zone-export.py @@ -23,4 +23,3 @@ # save to a file with open("example.com.zone", "w") as f: f.write(zone_file) - diff --git a/ns1/rest/zones.py b/ns1/rest/zones.py index 37c774f..a76e169 100644 --- a/ns1/rest/zones.py +++ b/ns1/rest/zones.py @@ -191,7 +191,7 @@ def delete_version(self, zone, version_id, callback=None, errback=None): def export(self, zone, callback=None, errback=None): """ Export zone as BIND-compatible zone file. - + :param str zone: zone name :return: zone file content as string """ diff --git a/ns1/zones.py b/ns1/zones.py index 5bcf0c1..e032aed 100644 --- a/ns1/zones.py +++ b/ns1/zones.py @@ -291,7 +291,7 @@ def usage(self, callback=None, errback=None, **kwargs): def export(self, callback=None, errback=None): """ Export zone as a BIND-compatible zone file. - + :param callback: optional callback :param errback: optional error callback :return: zone file content as string diff --git a/tests/unit/test_zone.py b/tests/unit/test_zone.py index 97b7014..1b1dda2 100644 --- a/tests/unit/test_zone.py +++ b/tests/unit/test_zone.py @@ -253,7 +253,6 @@ def test_rest_zone_buildbody(zones_config): assert z._buildBody(zone, **kwargs) == body - @pytest.mark.parametrize("zone, url", [("test.zone", "zones/test.zone/export")]) def test_rest_zone_export(zones_config, zone, url): z = ns1.rest.zones.Zones(zones_config) From f0d21b12b3ba71a52f1ed7c779b62f40f5ce67d2 Mon Sep 17 00:00:00 2001 From: soniafrancisNS1 Date: Wed, 17 Dec 2025 16:35:25 +0000 Subject: [PATCH 4/4] Fix black formatting for zone export code --- ns1/zones.py | 4 +--- tests/unit/test_zone.py | 4 +++- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ns1/zones.py b/ns1/zones.py index e032aed..1bd42ef 100644 --- a/ns1/zones.py +++ b/ns1/zones.py @@ -296,6 +296,4 @@ def export(self, callback=None, errback=None): :param errback: optional error callback :return: zone file content as string """ - return self._rest.export( - self.zone, callback=callback, errback=errback - ) + return self._rest.export(self.zone, callback=callback, errback=errback) diff --git a/tests/unit/test_zone.py b/tests/unit/test_zone.py index 1b1dda2..92dbb2b 100644 --- a/tests/unit/test_zone.py +++ b/tests/unit/test_zone.py @@ -253,7 +253,9 @@ def test_rest_zone_buildbody(zones_config): assert z._buildBody(zone, **kwargs) == body -@pytest.mark.parametrize("zone, url", [("test.zone", "zones/test.zone/export")]) +@pytest.mark.parametrize( + "zone, url", [("test.zone", "zones/test.zone/export")] +) def test_rest_zone_export(zones_config, zone, url): z = ns1.rest.zones.Zones(zones_config) z._make_request = mock.MagicMock()