├── .gitignore ├── CHANGES ├── LICENSE ├── README.md ├── VERSION ├── bin ├── pdns-copy-zone ├── pdns-create-zone └── pdns-remote-copy-zone ├── docs ├── Makefile ├── build │ └── html │ │ ├── .buildinfo │ │ ├── _modules │ │ ├── index.html │ │ ├── powerdns.html │ │ └── powerdns │ │ │ ├── client.html │ │ │ ├── exceptions.html │ │ │ └── interface.html │ │ ├── _sources │ │ ├── client.rst.txt │ │ ├── exceptions.rst.txt │ │ ├── index.rst.txt │ │ └── interface.rst.txt │ │ ├── _static │ │ ├── basic.css │ │ ├── caret-down.svg │ │ ├── classic.css │ │ ├── copybutton.js │ │ ├── custom.css │ │ ├── default.css │ │ ├── doctools.js │ │ ├── documentation_options.js │ │ ├── file.png │ │ ├── jquery-3.5.1.js │ │ ├── jquery.js │ │ ├── language_data.js │ │ ├── menu.js │ │ ├── minus.png │ │ ├── plus.png │ │ ├── py.png │ │ ├── py.svg │ │ ├── pydoctheme.css │ │ ├── pygments.css │ │ ├── searchtools.js │ │ ├── sidebar.js │ │ ├── underscore-1.13.1.js │ │ └── underscore.js │ │ ├── client.html │ │ ├── exceptions.html │ │ ├── genindex.html │ │ ├── index.html │ │ ├── interface.html │ │ ├── objects.inv │ │ ├── py-modindex.html │ │ ├── search.html │ │ └── searchindex.js ├── custom.css ├── html ├── index.html ├── requirements.txt └── source │ ├── client.rst │ ├── conf.py │ ├── exceptions.rst │ ├── index.rst │ └── interface.rst ├── files ├── Dockerfile └── pdns.conf ├── powerdns ├── __init__.py ├── client.py ├── exceptions.py └── interface.py ├── requirements.txt ├── setup.py └── tests ├── __init__.py ├── requirements.txt ├── test_client.py ├── test_exceptions.py └── test_interface.py /.gitignore: -------------------------------------------------------------------------------- 1 | build/* 2 | dist/* 3 | python_powerdns.egg-info/* 4 | *.pyc 5 | private_bin 6 | -------------------------------------------------------------------------------- /CHANGES: -------------------------------------------------------------------------------- 1 | python-powerdns - PowerDNS web api python client and interface 2 | ============================================================== 3 | 4 | Contact: Denis 'jawa' Pompilio 5 | Sources: https://github.com/outini/python-powerdns 6 | 7 | === v2.1.0 02/11/2021 === 8 | * feat(interface.py): Add support for RRSet Records as dicts (991jo) 9 | * feat(tests): Add tests suite and documentation 10 | 11 | === v2.0.0 15/06/2021 === 12 | * feat(interface.py): Add support for RRset comments 13 | 14 | === v1.0.0 01/06/2021 === 15 | * feat(interface.py): Add a notify() method for DNS zones notification 16 | 17 | === v0.2.5 08/10/2018 === 18 | * fix(interface.py): Allow list and not just tuple as a record type 19 | 20 | === v0.2.4 08/10/2018 === 21 | * fix(interface.py): rename method 22 | 23 | === v0.2.3 02/10/2018 === 24 | * feat(interface.py): Add update to deal with PDNS field 25 | 26 | === v0.2.2 10/09/2018 === 27 | * fix(interface.py): fix get_record method 28 | 29 | === v0.2.1 19/06/2018 === 30 | * Setup script improvement for PyPi publication 31 | 32 | === v0.2.0 19/06/2018 === 33 | * Modified setup.py for wheels creation 34 | * PyLint and documentation 35 | 36 | === v0.1.1 2018/02/13 === 37 | * New helper: pdns-zone-creator for DNS zone creation 38 | 39 | === v0.1.0 2018/02/08 === 40 | * Initial release of python-powerdns 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Denis 'jawa' Pompilio 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![PythonSupport][1]][1l] [![License][2]][2l] [![PyPI version][3]][3l] 2 | 3 | # python-powerdns -- PowerDNS web api python client and interface 4 | 5 | **Contact:** Denis 'jawa' Pompilio 6 | 7 | **Sources:** 8 | 9 | ## About 10 | 11 | This package provides intuitive and easy to use python client and interface 12 | for the PowerDNS web API. 13 | 14 | ## Installation 15 | 16 | ```bash 17 | python setup.py install 18 | ``` 19 | 20 | or 21 | 22 | ```bash 23 | pip install python-powerdns 24 | ``` 25 | 26 | ## Helpers 27 | 28 | ### pdns-zone-creator 29 | 30 | ```bash 31 | usage: pdns-create-zone [-h] -A API -K APIKEY -z ZONE -o ORIGIN -c ZONE -d DNS 32 | [-t TIMERS] 33 | 34 | PowerDNS zone creator 35 | 36 | optional arguments: 37 | -h, --help show this help message and exit 38 | -A API, --api API PowerDNS api (eg. https://api.domain.tld/api/v1 39 | -K APIKEY, --key APIKEY 40 | PowerDNS api key 41 | -z ZONE, --zone ZONE Zone name (canonical) 42 | -o ORIGIN, --origin ORIGIN 43 | Zone origin (for SOA) 44 | -c ZONE, --contact ZONE 45 | Zone contact (for SOA) 46 | -d DNS, --dns DNS Zone nameservers comma separated 47 | -t TIMERS, --timers TIMERS 48 | Zone timers (eg. '28800 7200 604800 86400') 49 | ``` 50 | 51 | ```bash 52 | ./bin/pdns-create-zone -A "https://api.domain.tld/api/v1" -K "xxxxxxxxx" \ 53 | -z "myzone.domain.tld." \ 54 | -o "ns01.domain.tld." -c "admin.domain.tld." \ 55 | -d "nsd01.domain.tld.,nsd02.domain.tld." 56 | powerdns.interface INFO: listing available PowerDNS servers 57 | powerdns.interface INFO: getting available servers from API 58 | powerdns.client INFO: request: GET https://api.domain.tld/api/v1/servers 59 | powerdns.client INFO: request response code: 200 60 | powerdns.interface INFO: 1 server(s) listed 61 | powerdns.interface INFO: creation of zone: myzone.domain.tld. 62 | powerdns.client INFO: request: POST https://api.domain.tld/api/v1/servers/localhost/zones 63 | powerdns.client INFO: request response code: 201 64 | powerdns.interface INFO: zone myzone.domain.tld. successfully created 65 | ``` 66 | 67 | ## Examples 68 | 69 | ### Basic initialization 70 | 71 | ```python 72 | import powerdns 73 | 74 | PDNS_API = "https://my.pdns.api.domain.tld/api/v1" 75 | PDNS_KEY = "mysupersecretbase64key" 76 | 77 | api_client = powerdns.PDNSApiClient(api_endpoint=PDNS_API, api_key=PDNS_KEY) 78 | api = powerdns.PDNSEndpoint(api_client) 79 | ``` 80 | 81 | ### Creation and deletion of zones 82 | 83 | ```python 84 | from datetime import date 85 | 86 | # Creating new zone on first PowerDNS server 87 | serial = date.today().strftime("%Y%m%d00") 88 | soa = "ns0.domain.tld. admin.domain.tld. %s 28800 7200 604800 86400" % serial 89 | soa_r = powerdns.RRSet(name='test.python-powerdns.domain.tld.', 90 | rtype="SOA", 91 | records=[(soa, False)], 92 | ttl=86400) 93 | zone = api.servers[0].create_zone(name="test.python-powerdns.domain.tld.", 94 | kind="Native", 95 | rrsets=[soa_r], 96 | nameservers=["ns1.domain.tld.", 97 | "ns2.domain.tld."]) 98 | 99 | # Getting new zone info 100 | print(zone) 101 | print(zone.details) 102 | 103 | # Deleting newly created zone 104 | api.servers[0].delete_zone(zone.name) 105 | ``` 106 | 107 | ### Creation and deletion of DNS records 108 | 109 | ```python 110 | zone = api.servers[0].get_zone("test.python-powerdns.domain.tld.") 111 | 112 | comments = [powerdns.Comment("test comment", "admin")] 113 | 114 | zone.create_records([ 115 | powerdns.RRSet('a', 'A', [('1.1.1.1', False)], comments=comments), 116 | powerdns.RRSet('b', 'A', ['1.1.1.2', '1.1.1.3']), 117 | powerdns.RRSet('c', 'A', [('1.1.1.4', False)]), 118 | powerdns.RRSet('d', 'CNAME', ['a']) 119 | ]) 120 | 121 | zone.delete_records([ 122 | powerdns.RRSet('a', 'A', [('1.1.1.1', False)]), 123 | powerdns.RRSet('d', 'CNAME', ['a']) 124 | ]) 125 | ``` 126 | 127 | Where (for the first RRSet): 128 | 129 | * `a` is the NAME of the record 130 | * `A` is the TYPE of the record 131 | * `[('1.1.1.1', False)]` is a list of RDATA entries (tuples or just strings), where: 132 | * `'1.1.1.1'` is the RDATA 133 | * `False` tells that this RDATA entry is NOT disabled 134 | 135 | ### Backup and restoration of zones 136 | 137 | ```python 138 | # Backup every zone of every PowerDNS server 139 | for server in api.servers: 140 | backup_dir = "backups/%s" % server.id 141 | for zone in server.zones: 142 | zone.backup(backup_dir) 143 | 144 | # Restore a single zone on first PowerDNS server 145 | zone_file = "backups/pdns-server-01/my.domain.tld.json" 146 | api.servers[0].restore_zone(zone_file) 147 | ``` 148 | 149 | ## Tests 150 | 151 | ### PowerDNS service 152 | 153 | A simple [Dockerfile] is provided to spawn a basic powerdns service for tests 154 | purposes. The container is built using: 155 | 156 | ```bash 157 | docker build --tag pdns . 158 | ``` 159 | 160 | And started using: 161 | 162 | ```bash 163 | docker run --rm -it pdns 164 | ``` 165 | 166 | ### Python Unit-Tests 167 | 168 | Python unit-tests are available in the [tests] directory. Based on [unittests], 169 | those are run using `coverage run -m unittest discover` or integrated in your 170 | IDE for development purposes. Those tests require a PDNS service to connect to 171 | (see _PowerDNS service_ section above). 172 | 173 | Those tests are very limited at the moment and will be improved in the future. 174 | 175 | ## License 176 | 177 | MIT LICENSE *(see LICENSE file)* 178 | 179 | ## Miscellaneous 180 | 181 | ``` 182 | ╚⊙ ⊙╝ 183 | ╚═(███)═╝ 184 | ╚═(███)═╝ 185 | ╚═(███)═╝ 186 | ╚═(███)═╝ 187 | ╚═(███)═╝ 188 | ╚═(███)═╝ 189 | ``` 190 | 191 | [1]: https://img.shields.io/badge/python-2.7,3.4+-blue.svg 192 | [1l]: https://github.com/outini/python-powerdns 193 | [2]: https://img.shields.io/badge/license-MIT-blue.svg 194 | [2l]: https://github.com/outini/python-powerdns 195 | [3]: https://badge.fury.io/py/python-powerdns.svg 196 | [3l]: https://pypi.org/project/python-powerdns 197 | [Dockerfile]: files/Dockerfile 198 | [tests]: tests 199 | [unittests]: https://docs.python.org/3/library/unittest.html 200 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 2.1.0 2 | -------------------------------------------------------------------------------- /bin/pdns-copy-zone: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # PowerDNS web api python client and interface (python-powerdns) 5 | # 6 | # Copyright (C) 2018 Denis Pompilio (jawa) 7 | # 8 | # This file is part of python-powerdns 9 | # 10 | # This program is free software; you can redistribute it and/or 11 | # modify it under the terms of the MIT License. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # MIT License for more details. 17 | # 18 | # You should have received a copy of the MIT License along with this 19 | # program; if not, see . 20 | 21 | import argparse 22 | import powerdns 23 | 24 | 25 | LOG = powerdns.basic_logger("powerdns") 26 | 27 | 28 | def ensure_canonical(name): 29 | """Ensure that name is canonical 30 | 31 | :param str name: Name 32 | :return: Canonical name 33 | :rtype: str 34 | """ 35 | if not name.endswith('.'): 36 | name += "." 37 | return name 38 | 39 | 40 | def copy_rrset(rrset, c_zone, n_zone): 41 | """Copy and transform rrset for new zone 42 | 43 | :param dict rrset: RRset data 44 | :param str c_zone: Current zone name (canonical) 45 | :param str n_zone: New zone name (canonical) 46 | :return: New RRset 47 | :rtype: dict 48 | """ 49 | if rrset['name'] == c_zone or rrset['name'].endswith("." + c_zone): 50 | rrset['name'] = rrset['name'].replace(c_zone, n_zone) 51 | 52 | if rrset['type'] == "CNAME": 53 | rrset['records'] = update_cname(rrset['records'], c_zone, n_zone) 54 | 55 | return rrset 56 | 57 | 58 | def update_cname(records, c_zone, n_zone): 59 | """ 60 | 61 | :param list records: CNAME RRset records list 62 | :param str c_zone: Current zone name (canonical) 63 | :param str n_zone: New zone name (canonical) 64 | :return: Updated records 65 | :rtype: list 66 | """ 67 | new_records = [] 68 | for rec in records: 69 | if rec['content'] == c_zone or rec['content'].endswith("." + c_zone): 70 | rec['content'] = rec['content'].replace(c_zone, n_zone) 71 | new_records.append(rec) 72 | return new_records 73 | 74 | 75 | # -- Main -- 76 | if __name__ == "__main__": 77 | parser = argparse.ArgumentParser(description='PowerDNS zone cloner') 78 | parser.add_argument('-A', '--api', dest='api', required=True, 79 | help="PowerDNS api (eg. https://api.domain.tld/api/v1") 80 | parser.add_argument('-K', '--key', dest='apikey', required=True, 81 | help="PowerDNS api key") 82 | parser.add_argument('-z', '--zone', dest='zone', required=True, 83 | help="Zone name (canonical)") 84 | parser.add_argument('-n', '--new-zone', dest='new_zone', required=True, 85 | help="New zone name (canonical)") 86 | parser.add_argument('-u', '--update', dest='u_zones', 87 | help="Also update impacted zones (comma separated)") 88 | 89 | args = parser.parse_args() 90 | 91 | api_client = powerdns.PDNSApiClient( 92 | api_endpoint=args.api, api_key=args.apikey, verify=False) 93 | api = powerdns.PDNSEndpoint(api_client) 94 | 95 | # ensure zone names are canonical 96 | zone_name = ensure_canonical(args.zone) 97 | new_zone_name = ensure_canonical(args.new_zone) 98 | 99 | if api.servers[0].get_zone(new_zone_name): 100 | print("New zone '%s' already exists!" % new_zone_name) 101 | exit() 102 | 103 | zone = api.servers[0].get_zone(zone_name) 104 | 105 | new_rrsets = [] 106 | for rrset in zone.details['rrsets']: 107 | new_rrsets.append(copy_rrset(rrset, zone_name, new_zone_name)) 108 | 109 | api.servers[0].create_zone( 110 | name=new_zone_name, 111 | kind=zone.details['kind'], 112 | masters=zone.details['masters'], 113 | nameservers=[], 114 | rrsets=new_rrsets 115 | ) 116 | 117 | print("New zone created: %s" % new_zone_name) 118 | 119 | if not args.u_zones: 120 | exit() 121 | 122 | u_zones = [ensure_canonical(name) for name in args.u_zones.split(',')] 123 | for uzone_name in u_zones: 124 | uzone = api.servers[0].get_zone(uzone_name) 125 | updated_rrsets = [] 126 | for rrset in uzone.details['rrsets']: 127 | if rrset['type'] == "CNAME": 128 | recs = update_cname(rrset['records'], zone_name, new_zone_name) 129 | updated_rrsets.append( 130 | powerdns.RRSet( 131 | name=rrset['name'], 132 | rtype=rrset['type'], 133 | ttl=rrset['ttl'], 134 | records=[tuple(rec.values()) for rec in recs] 135 | ) 136 | ) 137 | if updated_rrsets: 138 | uzone.create_records(updated_rrsets) 139 | print("Updated zone: %s" % uzone_name) 140 | else: 141 | print("Zone unchanged: %s" % uzone_name) 142 | -------------------------------------------------------------------------------- /bin/pdns-create-zone: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # PowerDNS web api python client and interface (python-powerdns) 5 | # 6 | # Copyright (C) 2018 Denis Pompilio (jawa) 7 | # 8 | # This file is part of python-powerdns 9 | # 10 | # This program is free software; you can redistribute it and/or 11 | # modify it under the terms of the MIT License. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # MIT License for more details. 17 | # 18 | # You should have received a copy of the MIT License along with this 19 | # program; if not, see . 20 | 21 | import argparse 22 | import powerdns 23 | from datetime import date 24 | 25 | 26 | logger = powerdns.basic_logger("powerdns") 27 | 28 | 29 | # -- Main -- 30 | if __name__ == "__main__": 31 | parser = argparse.ArgumentParser(description='PowerDNS zone creator') 32 | parser.add_argument('-A', '--api', dest='api', required=True, 33 | help="PowerDNS api (eg. https://api.domain.tld/api/v1") 34 | parser.add_argument('-K', '--key', dest='apikey', required=True, 35 | help="PowerDNS api key") 36 | parser.add_argument('-z', '--zone', dest='zone', required=True, 37 | help="Zone name (canonical)") 38 | parser.add_argument('-o', '--origin', dest='origin', required=True, 39 | help="Zone origin (for SOA)") 40 | parser.add_argument('-c', '--contact', dest='contact', required=True, 41 | help="Zone contact (for SOA)") 42 | parser.add_argument('-d', '--dns', dest='dns', required=True, 43 | help="Zone nameservers comma separated") 44 | parser.add_argument('-t', '--timers', dest='timers', 45 | help="Zone timers (eg. '28800 7200 604800 86400')", 46 | default="28800 7200 604800 86400") 47 | 48 | args = parser.parse_args() 49 | 50 | api_client = powerdns.PDNSApiClient( 51 | api_endpoint=args.api, api_key=args.apikey, verify=False) 52 | api = powerdns.PDNSEndpoint(api_client) 53 | 54 | zone_name = args.zone 55 | 56 | serial = date.today().strftime("%Y%m%d00") 57 | soa_ttl = args.timers.split()[-1] 58 | 59 | soa = "%s %s %s %s" % (args.origin, args.contact, serial, args.timers) 60 | soa_r = powerdns.RRSet(name=args.zone, 61 | rtype="SOA", 62 | records=[(soa, False)], 63 | ttl=soa_ttl) 64 | 65 | api.servers[0].create_zone(name=zone_name, 66 | kind="Native", 67 | rrsets=[soa_r], 68 | nameservers=args.dns.split(',')) 69 | -------------------------------------------------------------------------------- /bin/pdns-remote-copy-zone: -------------------------------------------------------------------------------- 1 | #! /usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # 4 | # PowerDNS web api python client and interface (python-powerdns) 5 | # 6 | # Copyright (C) 2018 Denis Pompilio (jawa) 7 | # 8 | # This file is part of python-powerdns 9 | # 10 | # This program is free software; you can redistribute it and/or 11 | # modify it under the terms of the MIT License. 12 | # 13 | # This program is distributed in the hope that it will be useful, 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 | # MIT License for more details. 17 | # 18 | # You should have received a copy of the MIT License along with this 19 | # program; if not, see . 20 | 21 | import argparse 22 | import powerdns 23 | 24 | 25 | LOG = powerdns.basic_logger("powerdns") 26 | 27 | 28 | def ensure_canonical(name): 29 | """Ensure that name is canonical 30 | 31 | :param str name: Name 32 | :return: Canonical name 33 | :rtype: str 34 | """ 35 | if not name.endswith('.'): 36 | name += "." 37 | return name 38 | 39 | 40 | def copy_rrset(rrset, c_zone, n_zone): 41 | """Copy and transform rrset for new zone 42 | 43 | :param dict rrset: RRset data 44 | :param str c_zone: Current zone name (canonical) 45 | :param str n_zone: New zone name (canonical) 46 | :return: New RRset 47 | :rtype: dict 48 | """ 49 | if rrset['name'] == c_zone or rrset['name'].endswith("." + c_zone): 50 | rrset['name'] = rrset['name'].replace(c_zone, n_zone) 51 | 52 | if rrset['type'] == "CNAME": 53 | rrset['records'] = update_cname(rrset['records'], c_zone, n_zone) 54 | 55 | return rrset 56 | 57 | 58 | def update_cname(records, c_zone, n_zone): 59 | """ 60 | 61 | :param list records: CNAME RRset records list 62 | :param str c_zone: Current zone name (canonical) 63 | :param str n_zone: New zone name (canonical) 64 | :return: Updated records 65 | :rtype: list 66 | """ 67 | new_records = [] 68 | for rec in records: 69 | if rec['content'] == c_zone or rec['content'].endswith("." + c_zone): 70 | rec['content'] = rec['content'].replace(c_zone, n_zone) 71 | new_records.append(rec) 72 | return new_records 73 | 74 | 75 | # -- Main -- 76 | if __name__ == "__main__": 77 | parser = argparse.ArgumentParser(description='PowerDNS zone cloner') 78 | parser.add_argument('-A', '--api-src', dest='api_src', required=True, 79 | help="PowerDNS source api (eg. https://api-src.domain.tld/api/v1") 80 | parser.add_argument('-B', '--api-dst', dest='api_dst', required=True, 81 | help="PowerDNS destination api (eg. https://api-dst.domain.tld/api/v1") 82 | parser.add_argument('-K', '--key-src', dest='apikey_src', required=True, 83 | help="PowerDNS source api key") 84 | parser.add_argument('-Q', '--key-dst', dest='apikey_dst', required=True, 85 | help="PowerDNS destination api key") 86 | parser.add_argument('-z', '--zone', dest='zone', required=True, 87 | help="Zone name (canonical)") 88 | parser.add_argument('-n', '--new-zone', dest='new_zone', required=True, 89 | help="New zone name (canonical)") 90 | parser.add_argument('-u', '--update', dest='u_zones', 91 | help="Also update impacted zones (comma separated)") 92 | 93 | args = parser.parse_args() 94 | 95 | api_client_src = powerdns.PDNSApiClient( 96 | api_endpoint=args.api_src, api_key=args.apikey_src, verify=False) 97 | api_source = powerdns.PDNSEndpoint(api_client_src) 98 | 99 | api_client_dst = powerdns.PDNSApiClient( 100 | api_endpoint=args.api_dst, api_key=args.apikey_dst, verify=False) 101 | api_destination = powerdns.PDNSEndpoint(api_client_dst) 102 | 103 | # ensure zone names are canonical 104 | zone_name = ensure_canonical(args.zone) 105 | new_zone_name = ensure_canonical(args.new_zone) 106 | 107 | if api_destination.servers[0].get_zone(new_zone_name): 108 | print("New zone '%s' already exists!" % new_zone_name) 109 | exit() 110 | 111 | zone = api_source.servers[0].get_zone(zone_name) 112 | 113 | new_rrsets = [] 114 | for rrset in zone.details['rrsets']: 115 | new_rrsets.append(copy_rrset(rrset, zone_name, new_zone_name)) 116 | 117 | api_destination.servers[0].create_zone( 118 | name=new_zone_name, 119 | kind=zone.details['kind'], 120 | masters=zone.details['masters'], 121 | nameservers=[], 122 | rrsets=new_rrsets 123 | ) 124 | 125 | print("New zone created: %s" % new_zone_name) 126 | 127 | if not args.u_zones: 128 | exit() 129 | 130 | u_zones = [ensure_canonical(name) for name in args.u_zones.split(',')] 131 | for uzone_name in u_zones: 132 | uzone = api_destination.servers[0].get_zone(uzone_name) 133 | updated_rrsets = [] 134 | for rrset in uzone.details['rrsets']: 135 | if rrset['type'] == "CNAME": 136 | recs = update_cname(rrset['records'], zone_name, new_zone_name) 137 | updated_rrsets.append( 138 | powerdns.RRSet( 139 | name=rrset['name'], 140 | rtype=rrset['type'], 141 | ttl=rrset['ttl'], 142 | records=[tuple(rec.values()) for rec in recs] 143 | ) 144 | ) 145 | if updated_rrsets: 146 | uzone.create_records(updated_rrsets) 147 | print("Updated zone: %s" % uzone_name) 148 | else: 149 | print("Zone unchanged: %s" % uzone_name) 150 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Minimal makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | SPHINXPROJ = python-powerdns 8 | SOURCEDIR = source 9 | BUILDDIR = build 10 | 11 | # Put it first so that "make" without argument is like "make help". 12 | help: 13 | @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 14 | 15 | .PHONY: help Makefile 16 | 17 | # Catch-all target: route all unknown targets to Sphinx using the new 18 | # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). 19 | html: Makefile 20 | @$(SPHINXBUILD) -M html "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 21 | cp "custom.css" "$(BUILDDIR)/html/_static/" 22 | 23 | %: Makefile 24 | @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) 25 | -------------------------------------------------------------------------------- /docs/build/html/.buildinfo: -------------------------------------------------------------------------------- 1 | # Sphinx build info version 1 2 | # This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done. 3 | config: 0aaf607029f57b8d9e72b2bb3c73d75b 4 | tags: 645f666f9bcd5a90fca523b33c5a78b7 5 | -------------------------------------------------------------------------------- /docs/build/html/_modules/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Overview: module code — python-powerdns 2.0.0 9 | documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 28 | 31 | 45 | 60 |
61 | 62 | 100 | 101 |
102 |
103 |
104 |
105 | 106 |

All modules for which code is available

107 | 112 | 113 |
114 |
115 |
116 |
117 | 131 |
132 |
133 | 171 | 192 | 193 | 194 | -------------------------------------------------------------------------------- /docs/build/html/_modules/powerdns.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | powerdns — python-powerdns 2.0.0 9 | documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 28 | 31 | 45 | 60 |
61 | 62 | 101 | 102 |
103 |
104 |
105 |
106 | 107 |

Source code for powerdns

108 | # -*- coding: utf-8 -*-
109 | #
110 | #  PowerDNS web api python client and interface (python-powerdns)
111 | #
112 | #  Copyright (C) 2018 Denis Pompilio (jawa) <denis.pompilio@gmail.com>
113 | #
114 | #  This file is part of python-powerdns
115 | #
116 | #  This program is free software; you can redistribute it and/or
117 | #  modify it under the terms of the MIT License.
118 | #
119 | #  This program is distributed in the hope that it will be useful,
120 | #  but WITHOUT ANY WARRANTY; without even the implied warranty of
121 | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
122 | #  MIT License for more details.
123 | #
124 | #  You should have received a copy of the MIT License along with this
125 | #  program; if not, see <https://opensource.org/licenses/MIT>.
126 | 
127 | """
128 | powerdns - PowerDNS API client and interface
129 | """
130 | 
131 | import logging
132 | from logging.handlers import SysLogHandler
133 | from .client import PDNSApiClient
134 | from .interface import PDNSEndpoint, RRSet, Comment
135 | 
136 | 
137 | #: Current version of the package as :class:`str`.
138 | __version__ = "2.0.0"
139 | 
140 | LOG_LEVELS = [
141 |     logging.ERROR,
142 |     logging.WARN,
143 |     logging.INFO,
144 |     logging.DEBUG
145 | ]
146 | 
147 | 
148 | 
[docs]def basic_logger(name=None, clevel=2, slevel=1): 149 | """Configure a basic logger 150 | 151 | :param str name: Logger name 152 | :param int clevel: Console log level 153 | :param int slevel: Syslog log level 154 | :return: Logger object 155 | """ 156 | logger = logging.getLogger(name) 157 | logger.setLevel(LOG_LEVELS[clevel]) 158 | fmt_syslog = logging.Formatter('%(name)s %(levelname)s: %(message)s') 159 | fmt_stream = logging.Formatter('%(name)s %(levelname)s: %(message)s') 160 | stream_handler = logging.StreamHandler() 161 | stream_handler.setFormatter(fmt_stream) 162 | logger.addHandler(stream_handler) 163 | syslog_handler = SysLogHandler(address='/dev/log') 164 | syslog_handler.setFormatter(fmt_syslog) 165 | syslog_handler.setLevel(LOG_LEVELS[slevel]) 166 | logger.addHandler(syslog_handler) 167 | return logger
168 |
169 | 170 |
171 |
172 |
173 |
174 | 188 |
189 |
190 | 229 | 250 | 251 | 252 | -------------------------------------------------------------------------------- /docs/build/html/_modules/powerdns/exceptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | powerdns.exceptions — python-powerdns 2.0.0 9 | documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 28 | 31 | 45 | 60 |
61 | 62 | 102 | 103 |
104 |
105 |
106 |
107 | 108 |

Source code for powerdns.exceptions

109 | # -*- coding: utf-8 -*-
110 | #
111 | #  PowerDNS web api python client and interface (python-powerdns)
112 | #
113 | #  Copyright (C) 2018 Denis Pompilio (jawa) <denis.pompilio@gmail.com>
114 | #
115 | #  This file is part of python-powerdns
116 | #
117 | #  This program is free software; you can redistribute it and/or
118 | #  modify it under the terms of the MIT License.
119 | #
120 | #  This program is distributed in the hope that it will be useful,
121 | #  but WITHOUT ANY WARRANTY; without even the implied warranty of
122 | #  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
123 | #  MIT License for more details.
124 | #
125 | #  You should have received a copy of the MIT License along with this
126 | #  program; if not, see <https://opensource.org/licenses/MIT>.
127 | 
128 | """
129 | powerdns.exceptions - PowerDNS API interface exceptions
130 | """
131 | 
132 | 
133 | 
[docs]class PDNSCanonicalError(SyntaxError): 134 | """PowerDNS Canonical Error 135 | """ 136 | def __init__(self, name): 137 | """Initialization""" 138 | self.name = name 139 | self.message = "'%s' is not canonical" % name 140 | super(PDNSCanonicalError, self).__init__()
141 | 142 | 143 |
[docs]class PDNSError(Exception): 144 | """PowerDNS API Exception 145 | """ 146 | def __str__(self): 147 | return "code=%d %s: %s" % (self.status_code, self.url, self.message) 148 | 149 | def __repr__(self): 150 | return "PDNSError(\"%s\", %d, \"%s\")" % (self.url, 151 | self.status_code, 152 | self.message) 153 | 154 | def __init__(self, url, status_code, message): 155 | """Initialization""" 156 | self.url = url 157 | self.status_code = status_code 158 | self.message = message 159 | super(PDNSError, self).__init__()
160 |
161 | 162 |
163 |
164 |
165 |
166 | 180 |
181 |
182 | 222 | 243 | 244 | 245 | -------------------------------------------------------------------------------- /docs/build/html/_sources/client.rst.txt: -------------------------------------------------------------------------------- 1 | python-powerdns -- Client 2 | ========================= 3 | 4 | .. autoclass:: powerdns.client.PDNSApiClient 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/build/html/_sources/exceptions.rst.txt: -------------------------------------------------------------------------------- 1 | python-powerdns -- Exceptions 2 | ============================= 3 | 4 | .. autoclass:: powerdns.exceptions.PDNSCanonicalError 5 | :members: 6 | 7 | .. autoclass:: powerdns.exceptions.PDNSError 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/build/html/_sources/index.rst.txt: -------------------------------------------------------------------------------- 1 | python-powerdns -- PowerDNS web api python client and interface 2 | =============================================================== 3 | 4 | .. module:: powerdns 5 | :synopsis: PowerDNS web api python client and interface. 6 | .. moduleauthor:: Denis 'jawa' Pompilio 7 | .. sectionauthor:: Denis 'jawa' Pompilio 8 | 9 | 10 | The :mod:`powerdns` package provides an intuitive and easy to use WEB API of 11 | PowerDNS admin. It defines the following attribute: 12 | 13 | .. autodata:: __version__ 14 | 15 | .. autofunction:: basic_logger 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | 20 | exceptions 21 | client 22 | interface 23 | -------------------------------------------------------------------------------- /docs/build/html/_sources/interface.rst.txt: -------------------------------------------------------------------------------- 1 | python-powerdns -- Interface 2 | ============================ 3 | 4 | .. autoclass:: powerdns.interface.PDNSEndpointBase 5 | :members: 6 | 7 | .. autoclass:: powerdns.interface.PDNSEndpoint 8 | :members: 9 | 10 | .. autoclass:: powerdns.interface.PDNSServer 11 | :members: 12 | 13 | .. autoclass:: powerdns.interface.PDNSZone 14 | :members: 15 | 16 | .. autoclass:: powerdns.interface.RRSet 17 | :members: 18 | 19 | .. autoclass:: powerdns.interface.Comment 20 | :members: 21 | -------------------------------------------------------------------------------- /docs/build/html/_static/caret-down.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /docs/build/html/_static/classic.css: -------------------------------------------------------------------------------- 1 | /* 2 | * classic.css_t 3 | * ~~~~~~~~~~~~~ 4 | * 5 | * Sphinx stylesheet -- classic theme. 6 | * 7 | * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | @import url("basic.css"); 13 | 14 | /* -- page layout ----------------------------------------------------------- */ 15 | 16 | html { 17 | /* CSS hack for macOS's scrollbar (see #1125) */ 18 | background-color: #FFFFFF; 19 | } 20 | 21 | body { 22 | font-family: 'Lucida Grande', Arial, sans-serif; 23 | font-size: 100%; 24 | background-color: white; 25 | color: #000; 26 | margin: 0; 27 | padding: 0; 28 | } 29 | 30 | div.document { 31 | background-color: white; 32 | } 33 | 34 | div.documentwrapper { 35 | float: left; 36 | width: 100%; 37 | } 38 | 39 | div.bodywrapper { 40 | margin: 0 0 0 230px; 41 | } 42 | 43 | div.body { 44 | background-color: white; 45 | color: #222222; 46 | padding: 0 20px 30px 20px; 47 | } 48 | 49 | div.footer { 50 | color: #555555; 51 | width: 100%; 52 | padding: 9px 0 9px 0; 53 | text-align: center; 54 | font-size: 75%; 55 | } 56 | 57 | div.footer a { 58 | color: #555555; 59 | text-decoration: underline; 60 | } 61 | 62 | div.related { 63 | background-color: white; 64 | line-height: 30px; 65 | color: #666666; 66 | } 67 | 68 | div.related a { 69 | color: #444444; 70 | } 71 | 72 | div.sphinxsidebar { 73 | } 74 | 75 | div.sphinxsidebar h3 { 76 | font-family: 'Lucida Grande', Arial, sans-serif; 77 | color: #444444; 78 | font-size: 1.4em; 79 | font-weight: normal; 80 | margin: 0; 81 | padding: 0; 82 | } 83 | 84 | div.sphinxsidebar h3 a { 85 | color: #444444; 86 | } 87 | 88 | div.sphinxsidebar h4 { 89 | font-family: 'Lucida Grande', Arial, sans-serif; 90 | color: #444444; 91 | font-size: 1.3em; 92 | font-weight: normal; 93 | margin: 5px 0 0 0; 94 | padding: 0; 95 | } 96 | 97 | div.sphinxsidebar p { 98 | color: #444444; 99 | } 100 | 101 | div.sphinxsidebar p.topless { 102 | margin: 5px 10px 10px 10px; 103 | } 104 | 105 | div.sphinxsidebar ul { 106 | margin: 10px; 107 | padding: 0; 108 | color: #444444; 109 | } 110 | 111 | div.sphinxsidebar a { 112 | color: #444444; 113 | } 114 | 115 | div.sphinxsidebar input { 116 | border: 1px solid #444444; 117 | font-family: sans-serif; 118 | font-size: 1em; 119 | } 120 | 121 | 122 | 123 | /* -- hyperlink styles ------------------------------------------------------ */ 124 | 125 | a { 126 | color: #0090c0; 127 | text-decoration: none; 128 | } 129 | 130 | a:visited { 131 | color: #00608f; 132 | text-decoration: none; 133 | } 134 | 135 | a:hover { 136 | text-decoration: underline; 137 | } 138 | 139 | 140 | 141 | /* -- body styles ----------------------------------------------------------- */ 142 | 143 | div.body h1, 144 | div.body h2, 145 | div.body h3, 146 | div.body h4, 147 | div.body h5, 148 | div.body h6 { 149 | font-family: 'Lucida Grande', Arial, sans-serif; 150 | background-color: white; 151 | font-weight: normal; 152 | color: #1a1a1a; 153 | border-bottom: 1px solid #ccc; 154 | margin: 20px -20px 10px -20px; 155 | padding: 3px 0 3px 10px; 156 | } 157 | 158 | div.body h1 { margin-top: 0; font-size: 200%; } 159 | div.body h2 { font-size: 160%; } 160 | div.body h3 { font-size: 140%; } 161 | div.body h4 { font-size: 120%; } 162 | div.body h5 { font-size: 110%; } 163 | div.body h6 { font-size: 100%; } 164 | 165 | a.headerlink { 166 | color: #aaaaaa; 167 | font-size: 0.8em; 168 | padding: 0 4px 0 4px; 169 | text-decoration: none; 170 | } 171 | 172 | a.headerlink:hover { 173 | background-color: #aaaaaa; 174 | color: white; 175 | } 176 | 177 | div.body p, div.body dd, div.body li, div.body blockquote { 178 | text-align: justify; 179 | line-height: 130%; 180 | } 181 | 182 | div.admonition p.admonition-title + p { 183 | display: inline; 184 | } 185 | 186 | div.admonition p { 187 | margin-bottom: 5px; 188 | } 189 | 190 | div.admonition pre { 191 | margin-bottom: 5px; 192 | } 193 | 194 | div.admonition ul, div.admonition ol { 195 | margin-bottom: 5px; 196 | } 197 | 198 | div.note { 199 | background-color: #eee; 200 | border: 1px solid #ccc; 201 | } 202 | 203 | div.seealso { 204 | background-color: #ffc; 205 | border: 1px solid #ff6; 206 | } 207 | 208 | div.topic { 209 | background-color: #eee; 210 | } 211 | 212 | div.warning { 213 | background-color: #ffe4e4; 214 | border: 1px solid #f66; 215 | } 216 | 217 | p.admonition-title { 218 | display: inline; 219 | } 220 | 221 | p.admonition-title:after { 222 | content: ":"; 223 | } 224 | 225 | pre { 226 | padding: 5px; 227 | background-color: #eeffcc; 228 | color: #333333; 229 | line-height: 120%; 230 | border: 1px solid #ac9; 231 | border-left: none; 232 | border-right: none; 233 | } 234 | 235 | code { 236 | background-color: #ecf0f3; 237 | padding: 0 1px 0 1px; 238 | font-size: 0.95em; 239 | } 240 | 241 | th, dl.field-list > dt { 242 | background-color: #ede; 243 | } 244 | 245 | .warning code { 246 | background: #efc2c2; 247 | } 248 | 249 | .note code { 250 | background: #d6d6d6; 251 | } 252 | 253 | .viewcode-back { 254 | font-family: 'Lucida Grande', Arial, sans-serif; 255 | } 256 | 257 | div.viewcode-block:target { 258 | background-color: #f4debf; 259 | border-top: 1px solid #ac9; 260 | border-bottom: 1px solid #ac9; 261 | } 262 | 263 | div.code-block-caption { 264 | color: #efefef; 265 | background-color: #1c4e63; 266 | } -------------------------------------------------------------------------------- /docs/build/html/_static/copybutton.js: -------------------------------------------------------------------------------- 1 | $(document).ready(function() { 2 | /* Add a [>>>] button on the top-right corner of code samples to hide 3 | * the >>> and ... prompts and the output and thus make the code 4 | * copyable. */ 5 | var div = $('.highlight-python .highlight,' + 6 | '.highlight-python3 .highlight,' + 7 | '.highlight-pycon .highlight,' + 8 | '.highlight-pycon3 .highlight,' + 9 | '.highlight-default .highlight'); 10 | var pre = div.find('pre'); 11 | 12 | // get the styles from the current theme 13 | pre.parent().parent().css('position', 'relative'); 14 | var hide_text = 'Hide the prompts and output'; 15 | var show_text = 'Show the prompts and output'; 16 | var border_width = pre.css('border-top-width'); 17 | var border_style = pre.css('border-top-style'); 18 | var border_color = pre.css('border-top-color'); 19 | var button_styles = { 20 | 'cursor':'pointer', 'position': 'absolute', 'top': '0', 'right': '0', 21 | 'border-color': border_color, 'border-style': border_style, 22 | 'border-width': border_width, 'color': border_color, 'text-size': '75%', 23 | 'font-family': 'monospace', 'padding-left': '0.2em', 'padding-right': '0.2em', 24 | 'border-radius': '0 3px 0 0' 25 | } 26 | 27 | // create and add the button to all the code blocks that contain >>> 28 | div.each(function(index) { 29 | var jthis = $(this); 30 | if (jthis.find('.gp').length > 0) { 31 | var button = $('>>>'); 32 | button.css(button_styles) 33 | button.attr('title', hide_text); 34 | button.data('hidden', 'false'); 35 | jthis.prepend(button); 36 | } 37 | // tracebacks (.gt) contain bare text elements that need to be 38 | // wrapped in a span to work with .nextUntil() (see later) 39 | jthis.find('pre:has(.gt)').contents().filter(function() { 40 | return ((this.nodeType == 3) && (this.data.trim().length > 0)); 41 | }).wrap(''); 42 | }); 43 | 44 | // define the behavior of the button when it's clicked 45 | $('.copybutton').click(function(e){ 46 | e.preventDefault(); 47 | var button = $(this); 48 | if (button.data('hidden') === 'false') { 49 | // hide the code output 50 | button.parent().find('.go, .gp, .gt').hide(); 51 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'hidden'); 52 | button.css('text-decoration', 'line-through'); 53 | button.attr('title', show_text); 54 | button.data('hidden', 'true'); 55 | } else { 56 | // show the code output 57 | button.parent().find('.go, .gp, .gt').show(); 58 | button.next('pre').find('.gt').nextUntil('.gp, .go').css('visibility', 'visible'); 59 | button.css('text-decoration', 'none'); 60 | button.attr('title', hide_text); 61 | button.data('hidden', 'false'); 62 | } 63 | }); 64 | }); 65 | -------------------------------------------------------------------------------- /docs/build/html/_static/custom.css: -------------------------------------------------------------------------------- 1 | 2 | @import url("pydoctheme.css"); 3 | 4 | div.body { 5 | min-width: 450px; 6 | max-width: 1200px; 7 | } 8 | -------------------------------------------------------------------------------- /docs/build/html/_static/default.css: -------------------------------------------------------------------------------- 1 | @import url("classic.css"); 2 | -------------------------------------------------------------------------------- /docs/build/html/_static/doctools.js: -------------------------------------------------------------------------------- 1 | /* 2 | * doctools.js 3 | * ~~~~~~~~~~~ 4 | * 5 | * Sphinx JavaScript utilities for all documentation. 6 | * 7 | * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 8 | * :license: BSD, see LICENSE for details. 9 | * 10 | */ 11 | 12 | /** 13 | * select a different prefix for underscore 14 | */ 15 | $u = _.noConflict(); 16 | 17 | /** 18 | * make the code below compatible with browsers without 19 | * an installed firebug like debugger 20 | if (!window.console || !console.firebug) { 21 | var names = ["log", "debug", "info", "warn", "error", "assert", "dir", 22 | "dirxml", "group", "groupEnd", "time", "timeEnd", "count", "trace", 23 | "profile", "profileEnd"]; 24 | window.console = {}; 25 | for (var i = 0; i < names.length; ++i) 26 | window.console[names[i]] = function() {}; 27 | } 28 | */ 29 | 30 | /** 31 | * small helper function to urldecode strings 32 | * 33 | * See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/decodeURIComponent#Decoding_query_parameters_from_a_URL 34 | */ 35 | jQuery.urldecode = function(x) { 36 | if (!x) { 37 | return x 38 | } 39 | return decodeURIComponent(x.replace(/\+/g, ' ')); 40 | }; 41 | 42 | /** 43 | * small helper function to urlencode strings 44 | */ 45 | jQuery.urlencode = encodeURIComponent; 46 | 47 | /** 48 | * This function returns the parsed url parameters of the 49 | * current request. Multiple values per key are supported, 50 | * it will always return arrays of strings for the value parts. 51 | */ 52 | jQuery.getQueryParameters = function(s) { 53 | if (typeof s === 'undefined') 54 | s = document.location.search; 55 | var parts = s.substr(s.indexOf('?') + 1).split('&'); 56 | var result = {}; 57 | for (var i = 0; i < parts.length; i++) { 58 | var tmp = parts[i].split('=', 2); 59 | var key = jQuery.urldecode(tmp[0]); 60 | var value = jQuery.urldecode(tmp[1]); 61 | if (key in result) 62 | result[key].push(value); 63 | else 64 | result[key] = [value]; 65 | } 66 | return result; 67 | }; 68 | 69 | /** 70 | * highlight a given string on a jquery object by wrapping it in 71 | * span elements with the given class name. 72 | */ 73 | jQuery.fn.highlightText = function(text, className) { 74 | function highlight(node, addItems) { 75 | if (node.nodeType === 3) { 76 | var val = node.nodeValue; 77 | var pos = val.toLowerCase().indexOf(text); 78 | if (pos >= 0 && 79 | !jQuery(node.parentNode).hasClass(className) && 80 | !jQuery(node.parentNode).hasClass("nohighlight")) { 81 | var span; 82 | var isInSVG = jQuery(node).closest("body, svg, foreignObject").is("svg"); 83 | if (isInSVG) { 84 | span = document.createElementNS("http://www.w3.org/2000/svg", "tspan"); 85 | } else { 86 | span = document.createElement("span"); 87 | span.className = className; 88 | } 89 | span.appendChild(document.createTextNode(val.substr(pos, text.length))); 90 | node.parentNode.insertBefore(span, node.parentNode.insertBefore( 91 | document.createTextNode(val.substr(pos + text.length)), 92 | node.nextSibling)); 93 | node.nodeValue = val.substr(0, pos); 94 | if (isInSVG) { 95 | var rect = document.createElementNS("http://www.w3.org/2000/svg", "rect"); 96 | var bbox = node.parentElement.getBBox(); 97 | rect.x.baseVal.value = bbox.x; 98 | rect.y.baseVal.value = bbox.y; 99 | rect.width.baseVal.value = bbox.width; 100 | rect.height.baseVal.value = bbox.height; 101 | rect.setAttribute('class', className); 102 | addItems.push({ 103 | "parent": node.parentNode, 104 | "target": rect}); 105 | } 106 | } 107 | } 108 | else if (!jQuery(node).is("button, select, textarea")) { 109 | jQuery.each(node.childNodes, function() { 110 | highlight(this, addItems); 111 | }); 112 | } 113 | } 114 | var addItems = []; 115 | var result = this.each(function() { 116 | highlight(this, addItems); 117 | }); 118 | for (var i = 0; i < addItems.length; ++i) { 119 | jQuery(addItems[i].parent).before(addItems[i].target); 120 | } 121 | return result; 122 | }; 123 | 124 | /* 125 | * backward compatibility for jQuery.browser 126 | * This will be supported until firefox bug is fixed. 127 | */ 128 | if (!jQuery.browser) { 129 | jQuery.uaMatch = function(ua) { 130 | ua = ua.toLowerCase(); 131 | 132 | var match = /(chrome)[ \/]([\w.]+)/.exec(ua) || 133 | /(webkit)[ \/]([\w.]+)/.exec(ua) || 134 | /(opera)(?:.*version|)[ \/]([\w.]+)/.exec(ua) || 135 | /(msie) ([\w.]+)/.exec(ua) || 136 | ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec(ua) || 137 | []; 138 | 139 | return { 140 | browser: match[ 1 ] || "", 141 | version: match[ 2 ] || "0" 142 | }; 143 | }; 144 | jQuery.browser = {}; 145 | jQuery.browser[jQuery.uaMatch(navigator.userAgent).browser] = true; 146 | } 147 | 148 | /** 149 | * Small JavaScript module for the documentation. 150 | */ 151 | var Documentation = { 152 | 153 | init : function() { 154 | this.fixFirefoxAnchorBug(); 155 | this.highlightSearchWords(); 156 | this.initIndexTable(); 157 | if (DOCUMENTATION_OPTIONS.NAVIGATION_WITH_KEYS) { 158 | this.initOnKeyListeners(); 159 | } 160 | }, 161 | 162 | /** 163 | * i18n support 164 | */ 165 | TRANSLATIONS : {}, 166 | PLURAL_EXPR : function(n) { return n === 1 ? 0 : 1; }, 167 | LOCALE : 'unknown', 168 | 169 | // gettext and ngettext don't access this so that the functions 170 | // can safely bound to a different name (_ = Documentation.gettext) 171 | gettext : function(string) { 172 | var translated = Documentation.TRANSLATIONS[string]; 173 | if (typeof translated === 'undefined') 174 | return string; 175 | return (typeof translated === 'string') ? translated : translated[0]; 176 | }, 177 | 178 | ngettext : function(singular, plural, n) { 179 | var translated = Documentation.TRANSLATIONS[singular]; 180 | if (typeof translated === 'undefined') 181 | return (n == 1) ? singular : plural; 182 | return translated[Documentation.PLURALEXPR(n)]; 183 | }, 184 | 185 | addTranslations : function(catalog) { 186 | for (var key in catalog.messages) 187 | this.TRANSLATIONS[key] = catalog.messages[key]; 188 | this.PLURAL_EXPR = new Function('n', 'return +(' + catalog.plural_expr + ')'); 189 | this.LOCALE = catalog.locale; 190 | }, 191 | 192 | /** 193 | * add context elements like header anchor links 194 | */ 195 | addContextElements : function() { 196 | $('div[id] > :header:first').each(function() { 197 | $('\u00B6'). 198 | attr('href', '#' + this.id). 199 | attr('title', _('Permalink to this headline')). 200 | appendTo(this); 201 | }); 202 | $('dt[id]').each(function() { 203 | $('\u00B6'). 204 | attr('href', '#' + this.id). 205 | attr('title', _('Permalink to this definition')). 206 | appendTo(this); 207 | }); 208 | }, 209 | 210 | /** 211 | * workaround a firefox stupidity 212 | * see: https://bugzilla.mozilla.org/show_bug.cgi?id=645075 213 | */ 214 | fixFirefoxAnchorBug : function() { 215 | if (document.location.hash && $.browser.mozilla) 216 | window.setTimeout(function() { 217 | document.location.href += ''; 218 | }, 10); 219 | }, 220 | 221 | /** 222 | * highlight the search words provided in the url in the text 223 | */ 224 | highlightSearchWords : function() { 225 | var params = $.getQueryParameters(); 226 | var terms = (params.highlight) ? params.highlight[0].split(/\s+/) : []; 227 | if (terms.length) { 228 | var body = $('div.body'); 229 | if (!body.length) { 230 | body = $('body'); 231 | } 232 | window.setTimeout(function() { 233 | $.each(terms, function() { 234 | body.highlightText(this.toLowerCase(), 'highlighted'); 235 | }); 236 | }, 10); 237 | $('') 239 | .appendTo($('#searchbox')); 240 | } 241 | }, 242 | 243 | /** 244 | * init the domain index toggle buttons 245 | */ 246 | initIndexTable : function() { 247 | var togglers = $('img.toggler').click(function() { 248 | var src = $(this).attr('src'); 249 | var idnum = $(this).attr('id').substr(7); 250 | $('tr.cg-' + idnum).toggle(); 251 | if (src.substr(-9) === 'minus.png') 252 | $(this).attr('src', src.substr(0, src.length-9) + 'plus.png'); 253 | else 254 | $(this).attr('src', src.substr(0, src.length-8) + 'minus.png'); 255 | }).css('display', ''); 256 | if (DOCUMENTATION_OPTIONS.COLLAPSE_INDEX) { 257 | togglers.click(); 258 | } 259 | }, 260 | 261 | /** 262 | * helper function to hide the search marks again 263 | */ 264 | hideSearchWords : function() { 265 | $('#searchbox .highlight-link').fadeOut(300); 266 | $('span.highlighted').removeClass('highlighted'); 267 | }, 268 | 269 | /** 270 | * make the url absolute 271 | */ 272 | makeURL : function(relativeURL) { 273 | return DOCUMENTATION_OPTIONS.URL_ROOT + '/' + relativeURL; 274 | }, 275 | 276 | /** 277 | * get the current relative url 278 | */ 279 | getCurrentURL : function() { 280 | var path = document.location.pathname; 281 | var parts = path.split(/\//); 282 | $.each(DOCUMENTATION_OPTIONS.URL_ROOT.split(/\//), function() { 283 | if (this === '..') 284 | parts.pop(); 285 | }); 286 | var url = parts.join('/'); 287 | return path.substring(url.lastIndexOf('/') + 1, path.length - 1); 288 | }, 289 | 290 | initOnKeyListeners: function() { 291 | $(document).keydown(function(event) { 292 | var activeElementType = document.activeElement.tagName; 293 | // don't navigate when in search box, textarea, dropdown or button 294 | if (activeElementType !== 'TEXTAREA' && activeElementType !== 'INPUT' && activeElementType !== 'SELECT' 295 | && activeElementType !== 'BUTTON' && !event.altKey && !event.ctrlKey && !event.metaKey 296 | && !event.shiftKey) { 297 | switch (event.keyCode) { 298 | case 37: // left 299 | var prevHref = $('link[rel="prev"]').prop('href'); 300 | if (prevHref) { 301 | window.location.href = prevHref; 302 | return false; 303 | } 304 | case 39: // right 305 | var nextHref = $('link[rel="next"]').prop('href'); 306 | if (nextHref) { 307 | window.location.href = nextHref; 308 | return false; 309 | } 310 | } 311 | } 312 | }); 313 | } 314 | }; 315 | 316 | // quick alias for translations 317 | _ = Documentation.gettext; 318 | 319 | $(document).ready(function() { 320 | Documentation.init(); 321 | }); 322 | -------------------------------------------------------------------------------- /docs/build/html/_static/documentation_options.js: -------------------------------------------------------------------------------- 1 | var DOCUMENTATION_OPTIONS = { 2 | URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'), 3 | VERSION: '2.0.0', 4 | LANGUAGE: 'None', 5 | COLLAPSE_INDEX: false, 6 | BUILDER: 'html', 7 | FILE_SUFFIX: '.html', 8 | LINK_SUFFIX: '.html', 9 | HAS_SOURCE: true, 10 | SOURCELINK_SUFFIX: '.txt', 11 | NAVIGATION_WITH_KEYS: false 12 | }; -------------------------------------------------------------------------------- /docs/build/html/_static/file.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outini/python-powerdns/8caa8e283dca8609e91dcc67785366bb412d527f/docs/build/html/_static/file.png -------------------------------------------------------------------------------- /docs/build/html/_static/language_data.js: -------------------------------------------------------------------------------- 1 | /* 2 | * language_data.js 3 | * ~~~~~~~~~~~~~~~~ 4 | * 5 | * This script contains the language-specific data used by searchtools.js, 6 | * namely the list of stopwords, stemmer, scorer and splitter. 7 | * 8 | * :copyright: Copyright 2007-2021 by the Sphinx team, see AUTHORS. 9 | * :license: BSD, see LICENSE for details. 10 | * 11 | */ 12 | 13 | var stopwords = ["a","and","are","as","at","be","but","by","for","if","in","into","is","it","near","no","not","of","on","or","such","that","the","their","then","there","these","they","this","to","was","will","with"]; 14 | 15 | 16 | /* Non-minified version is copied as a separate JS file, is available */ 17 | 18 | /** 19 | * Porter Stemmer 20 | */ 21 | var Stemmer = function() { 22 | 23 | var step2list = { 24 | ational: 'ate', 25 | tional: 'tion', 26 | enci: 'ence', 27 | anci: 'ance', 28 | izer: 'ize', 29 | bli: 'ble', 30 | alli: 'al', 31 | entli: 'ent', 32 | eli: 'e', 33 | ousli: 'ous', 34 | ization: 'ize', 35 | ation: 'ate', 36 | ator: 'ate', 37 | alism: 'al', 38 | iveness: 'ive', 39 | fulness: 'ful', 40 | ousness: 'ous', 41 | aliti: 'al', 42 | iviti: 'ive', 43 | biliti: 'ble', 44 | logi: 'log' 45 | }; 46 | 47 | var step3list = { 48 | icate: 'ic', 49 | ative: '', 50 | alize: 'al', 51 | iciti: 'ic', 52 | ical: 'ic', 53 | ful: '', 54 | ness: '' 55 | }; 56 | 57 | var c = "[^aeiou]"; // consonant 58 | var v = "[aeiouy]"; // vowel 59 | var C = c + "[^aeiouy]*"; // consonant sequence 60 | var V = v + "[aeiou]*"; // vowel sequence 61 | 62 | var mgr0 = "^(" + C + ")?" + V + C; // [C]VC... is m>0 63 | var meq1 = "^(" + C + ")?" + V + C + "(" + V + ")?$"; // [C]VC[V] is m=1 64 | var mgr1 = "^(" + C + ")?" + V + C + V + C; // [C]VCVC... is m>1 65 | var s_v = "^(" + C + ")?" + v; // vowel in stem 66 | 67 | this.stemWord = function (w) { 68 | var stem; 69 | var suffix; 70 | var firstch; 71 | var origword = w; 72 | 73 | if (w.length < 3) 74 | return w; 75 | 76 | var re; 77 | var re2; 78 | var re3; 79 | var re4; 80 | 81 | firstch = w.substr(0,1); 82 | if (firstch == "y") 83 | w = firstch.toUpperCase() + w.substr(1); 84 | 85 | // Step 1a 86 | re = /^(.+?)(ss|i)es$/; 87 | re2 = /^(.+?)([^s])s$/; 88 | 89 | if (re.test(w)) 90 | w = w.replace(re,"$1$2"); 91 | else if (re2.test(w)) 92 | w = w.replace(re2,"$1$2"); 93 | 94 | // Step 1b 95 | re = /^(.+?)eed$/; 96 | re2 = /^(.+?)(ed|ing)$/; 97 | if (re.test(w)) { 98 | var fp = re.exec(w); 99 | re = new RegExp(mgr0); 100 | if (re.test(fp[1])) { 101 | re = /.$/; 102 | w = w.replace(re,""); 103 | } 104 | } 105 | else if (re2.test(w)) { 106 | var fp = re2.exec(w); 107 | stem = fp[1]; 108 | re2 = new RegExp(s_v); 109 | if (re2.test(stem)) { 110 | w = stem; 111 | re2 = /(at|bl|iz)$/; 112 | re3 = new RegExp("([^aeiouylsz])\\1$"); 113 | re4 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 114 | if (re2.test(w)) 115 | w = w + "e"; 116 | else if (re3.test(w)) { 117 | re = /.$/; 118 | w = w.replace(re,""); 119 | } 120 | else if (re4.test(w)) 121 | w = w + "e"; 122 | } 123 | } 124 | 125 | // Step 1c 126 | re = /^(.+?)y$/; 127 | if (re.test(w)) { 128 | var fp = re.exec(w); 129 | stem = fp[1]; 130 | re = new RegExp(s_v); 131 | if (re.test(stem)) 132 | w = stem + "i"; 133 | } 134 | 135 | // Step 2 136 | re = /^(.+?)(ational|tional|enci|anci|izer|bli|alli|entli|eli|ousli|ization|ation|ator|alism|iveness|fulness|ousness|aliti|iviti|biliti|logi)$/; 137 | if (re.test(w)) { 138 | var fp = re.exec(w); 139 | stem = fp[1]; 140 | suffix = fp[2]; 141 | re = new RegExp(mgr0); 142 | if (re.test(stem)) 143 | w = stem + step2list[suffix]; 144 | } 145 | 146 | // Step 3 147 | re = /^(.+?)(icate|ative|alize|iciti|ical|ful|ness)$/; 148 | if (re.test(w)) { 149 | var fp = re.exec(w); 150 | stem = fp[1]; 151 | suffix = fp[2]; 152 | re = new RegExp(mgr0); 153 | if (re.test(stem)) 154 | w = stem + step3list[suffix]; 155 | } 156 | 157 | // Step 4 158 | re = /^(.+?)(al|ance|ence|er|ic|able|ible|ant|ement|ment|ent|ou|ism|ate|iti|ous|ive|ize)$/; 159 | re2 = /^(.+?)(s|t)(ion)$/; 160 | if (re.test(w)) { 161 | var fp = re.exec(w); 162 | stem = fp[1]; 163 | re = new RegExp(mgr1); 164 | if (re.test(stem)) 165 | w = stem; 166 | } 167 | else if (re2.test(w)) { 168 | var fp = re2.exec(w); 169 | stem = fp[1] + fp[2]; 170 | re2 = new RegExp(mgr1); 171 | if (re2.test(stem)) 172 | w = stem; 173 | } 174 | 175 | // Step 5 176 | re = /^(.+?)e$/; 177 | if (re.test(w)) { 178 | var fp = re.exec(w); 179 | stem = fp[1]; 180 | re = new RegExp(mgr1); 181 | re2 = new RegExp(meq1); 182 | re3 = new RegExp("^" + C + v + "[^aeiouwxy]$"); 183 | if (re.test(stem) || (re2.test(stem) && !(re3.test(stem)))) 184 | w = stem; 185 | } 186 | re = /ll$/; 187 | re2 = new RegExp(mgr1); 188 | if (re.test(w) && re2.test(w)) { 189 | re = /.$/; 190 | w = w.replace(re,""); 191 | } 192 | 193 | // and turn initial Y back to y 194 | if (firstch == "y") 195 | w = firstch.toLowerCase() + w.substr(1); 196 | return w; 197 | } 198 | } 199 | 200 | 201 | 202 | 203 | var splitChars = (function() { 204 | var result = {}; 205 | var singles = [96, 180, 187, 191, 215, 247, 749, 885, 903, 907, 909, 930, 1014, 1648, 206 | 1748, 1809, 2416, 2473, 2481, 2526, 2601, 2609, 2612, 2615, 2653, 2702, 207 | 2706, 2729, 2737, 2740, 2857, 2865, 2868, 2910, 2928, 2948, 2961, 2971, 208 | 2973, 3085, 3089, 3113, 3124, 3213, 3217, 3241, 3252, 3295, 3341, 3345, 209 | 3369, 3506, 3516, 3633, 3715, 3721, 3736, 3744, 3748, 3750, 3756, 3761, 210 | 3781, 3912, 4239, 4347, 4681, 4695, 4697, 4745, 4785, 4799, 4801, 4823, 211 | 4881, 5760, 5901, 5997, 6313, 7405, 8024, 8026, 8028, 8030, 8117, 8125, 212 | 8133, 8181, 8468, 8485, 8487, 8489, 8494, 8527, 11311, 11359, 11687, 11695, 213 | 11703, 11711, 11719, 11727, 11735, 12448, 12539, 43010, 43014, 43019, 43587, 214 | 43696, 43713, 64286, 64297, 64311, 64317, 64319, 64322, 64325, 65141]; 215 | var i, j, start, end; 216 | for (i = 0; i < singles.length; i++) { 217 | result[singles[i]] = true; 218 | } 219 | var ranges = [[0, 47], [58, 64], [91, 94], [123, 169], [171, 177], [182, 184], [706, 709], 220 | [722, 735], [741, 747], [751, 879], [888, 889], [894, 901], [1154, 1161], 221 | [1318, 1328], [1367, 1368], [1370, 1376], [1416, 1487], [1515, 1519], [1523, 1568], 222 | [1611, 1631], [1642, 1645], [1750, 1764], [1767, 1773], [1789, 1790], [1792, 1807], 223 | [1840, 1868], [1958, 1968], [1970, 1983], [2027, 2035], [2038, 2041], [2043, 2047], 224 | [2070, 2073], [2075, 2083], [2085, 2087], [2089, 2307], [2362, 2364], [2366, 2383], 225 | [2385, 2391], [2402, 2405], [2419, 2424], [2432, 2436], [2445, 2446], [2449, 2450], 226 | [2483, 2485], [2490, 2492], [2494, 2509], [2511, 2523], [2530, 2533], [2546, 2547], 227 | [2554, 2564], [2571, 2574], [2577, 2578], [2618, 2648], [2655, 2661], [2672, 2673], 228 | [2677, 2692], [2746, 2748], [2750, 2767], [2769, 2783], [2786, 2789], [2800, 2820], 229 | [2829, 2830], [2833, 2834], [2874, 2876], [2878, 2907], [2914, 2917], [2930, 2946], 230 | [2955, 2957], [2966, 2968], [2976, 2978], [2981, 2983], [2987, 2989], [3002, 3023], 231 | [3025, 3045], [3059, 3076], [3130, 3132], [3134, 3159], [3162, 3167], [3170, 3173], 232 | [3184, 3191], [3199, 3204], [3258, 3260], [3262, 3293], [3298, 3301], [3312, 3332], 233 | [3386, 3388], [3390, 3423], [3426, 3429], [3446, 3449], [3456, 3460], [3479, 3481], 234 | [3518, 3519], [3527, 3584], [3636, 3647], [3655, 3663], [3674, 3712], [3717, 3718], 235 | [3723, 3724], [3726, 3731], [3752, 3753], [3764, 3772], [3774, 3775], [3783, 3791], 236 | [3802, 3803], [3806, 3839], [3841, 3871], [3892, 3903], [3949, 3975], [3980, 4095], 237 | [4139, 4158], [4170, 4175], [4182, 4185], [4190, 4192], [4194, 4196], [4199, 4205], 238 | [4209, 4212], [4226, 4237], [4250, 4255], [4294, 4303], [4349, 4351], [4686, 4687], 239 | [4702, 4703], [4750, 4751], [4790, 4791], [4806, 4807], [4886, 4887], [4955, 4968], 240 | [4989, 4991], [5008, 5023], [5109, 5120], [5741, 5742], [5787, 5791], [5867, 5869], 241 | [5873, 5887], [5906, 5919], [5938, 5951], [5970, 5983], [6001, 6015], [6068, 6102], 242 | [6104, 6107], [6109, 6111], [6122, 6127], [6138, 6159], [6170, 6175], [6264, 6271], 243 | [6315, 6319], [6390, 6399], [6429, 6469], [6510, 6511], [6517, 6527], [6572, 6592], 244 | [6600, 6607], [6619, 6655], [6679, 6687], [6741, 6783], [6794, 6799], [6810, 6822], 245 | [6824, 6916], [6964, 6980], [6988, 6991], [7002, 7042], [7073, 7085], [7098, 7167], 246 | [7204, 7231], [7242, 7244], [7294, 7400], [7410, 7423], [7616, 7679], [7958, 7959], 247 | [7966, 7967], [8006, 8007], [8014, 8015], [8062, 8063], [8127, 8129], [8141, 8143], 248 | [8148, 8149], [8156, 8159], [8173, 8177], [8189, 8303], [8306, 8307], [8314, 8318], 249 | [8330, 8335], [8341, 8449], [8451, 8454], [8456, 8457], [8470, 8472], [8478, 8483], 250 | [8506, 8507], [8512, 8516], [8522, 8525], [8586, 9311], [9372, 9449], [9472, 10101], 251 | [10132, 11263], [11493, 11498], [11503, 11516], [11518, 11519], [11558, 11567], 252 | [11622, 11630], [11632, 11647], [11671, 11679], [11743, 11822], [11824, 12292], 253 | [12296, 12320], [12330, 12336], [12342, 12343], [12349, 12352], [12439, 12444], 254 | [12544, 12548], [12590, 12592], [12687, 12689], [12694, 12703], [12728, 12783], 255 | [12800, 12831], [12842, 12880], [12896, 12927], [12938, 12976], [12992, 13311], 256 | [19894, 19967], [40908, 40959], [42125, 42191], [42238, 42239], [42509, 42511], 257 | [42540, 42559], [42592, 42593], [42607, 42622], [42648, 42655], [42736, 42774], 258 | [42784, 42785], [42889, 42890], [42893, 43002], [43043, 43055], [43062, 43071], 259 | [43124, 43137], [43188, 43215], [43226, 43249], [43256, 43258], [43260, 43263], 260 | [43302, 43311], [43335, 43359], [43389, 43395], [43443, 43470], [43482, 43519], 261 | [43561, 43583], [43596, 43599], [43610, 43615], [43639, 43641], [43643, 43647], 262 | [43698, 43700], [43703, 43704], [43710, 43711], [43715, 43738], [43742, 43967], 263 | [44003, 44015], [44026, 44031], [55204, 55215], [55239, 55242], [55292, 55295], 264 | [57344, 63743], [64046, 64047], [64110, 64111], [64218, 64255], [64263, 64274], 265 | [64280, 64284], [64434, 64466], [64830, 64847], [64912, 64913], [64968, 65007], 266 | [65020, 65135], [65277, 65295], [65306, 65312], [65339, 65344], [65371, 65381], 267 | [65471, 65473], [65480, 65481], [65488, 65489], [65496, 65497]]; 268 | for (i = 0; i < ranges.length; i++) { 269 | start = ranges[i][0]; 270 | end = ranges[i][1]; 271 | for (j = start; j <= end; j++) { 272 | result[j] = true; 273 | } 274 | } 275 | return result; 276 | })(); 277 | 278 | function splitQuery(query) { 279 | var result = []; 280 | var start = -1; 281 | for (var i = 0; i < query.length; i++) { 282 | if (splitChars[query.charCodeAt(i)]) { 283 | if (start !== -1) { 284 | result.push(query.slice(start, i)); 285 | start = -1; 286 | } 287 | } else if (start === -1) { 288 | start = i; 289 | } 290 | } 291 | if (start !== -1) { 292 | result.push(query.slice(start)); 293 | } 294 | return result; 295 | } 296 | 297 | 298 | -------------------------------------------------------------------------------- /docs/build/html/_static/menu.js: -------------------------------------------------------------------------------- 1 | document.addEventListener('DOMContentLoaded', function () { 2 | 3 | // Make tables responsive by wrapping them in a div and making them scrollable 4 | const tables = document.querySelectorAll('table.docutils'); 5 | tables.forEach(function(table){ 6 | table.outerHTML = '
' + table.outerHTML + '
' 7 | }); 8 | 9 | const togglerInput = document.querySelector('.toggler__input'); 10 | const togglerLabel = document.querySelector('.toggler__label'); 11 | const sideMenu = document.querySelector('.menu-wrapper'); 12 | const menuItems = document.querySelectorAll('.menu') 13 | const doc = document.querySelector('.document'); 14 | const body = document.querySelector('body'); 15 | 16 | function closeMenu() { 17 | togglerInput.checked = false; 18 | sideMenu.setAttribute("aria-expanded", 'false'); 19 | sideMenu.setAttribute('aria-hidden', 'true'); 20 | togglerLabel.setAttribute('aria-pressed', 'false'); 21 | body.style.overflow = 'visible'; 22 | } 23 | function openMenu() { 24 | togglerInput.checked = true; 25 | sideMenu.setAttribute("aria-expanded", 'true'); 26 | sideMenu.setAttribute('aria-hidden', 'false'); 27 | togglerLabel.setAttribute('aria-pressed', 'true'); 28 | body.style.overflow = 'hidden'; 29 | } 30 | 31 | // Close menu when link on the sideMenu is clicked 32 | sideMenu.addEventListener('click', function (event) { 33 | let target = event.target; 34 | if (target.tagName.toLowerCase() !== 'a') return; 35 | closeMenu(); 36 | }) 37 | // Add accessibility data when sideMenu is opened/closed 38 | togglerInput.addEventListener('change', function (e) { 39 | togglerInput.checked ? openMenu() : closeMenu(); 40 | }); 41 | // Make sideMenu links tabbable only when visible 42 | for(let menuItem of menuItems) { 43 | if(togglerInput.checked) { 44 | menuItem.setAttribute('tabindex', '0'); 45 | } else { 46 | menuItem.setAttribute('tabindex', '-1'); 47 | } 48 | } 49 | // Close sideMenu when document body is clicked 50 | doc.addEventListener('click', function () { 51 | if (togglerInput.checked) { 52 | closeMenu(); 53 | } 54 | }) 55 | }) -------------------------------------------------------------------------------- /docs/build/html/_static/minus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outini/python-powerdns/8caa8e283dca8609e91dcc67785366bb412d527f/docs/build/html/_static/minus.png -------------------------------------------------------------------------------- /docs/build/html/_static/plus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outini/python-powerdns/8caa8e283dca8609e91dcc67785366bb412d527f/docs/build/html/_static/plus.png -------------------------------------------------------------------------------- /docs/build/html/_static/py.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outini/python-powerdns/8caa8e283dca8609e91dcc67785366bb412d527f/docs/build/html/_static/py.png -------------------------------------------------------------------------------- /docs/build/html/_static/py.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /docs/build/html/_static/pydoctheme.css: -------------------------------------------------------------------------------- 1 | @import url("default.css"); 2 | 3 | body { 4 | background-color: white; 5 | margin-left: 1em; 6 | margin-right: 1em; 7 | } 8 | 9 | .mobile-nav, 10 | .menu-wrapper { 11 | display: none; 12 | } 13 | 14 | div.related { 15 | margin-bottom: 1.2em; 16 | padding: 0.5em 0; 17 | border-bottom: 1px solid #ccc; 18 | margin-top: 0.5em; 19 | } 20 | 21 | div.related a:hover { 22 | color: #0095C4; 23 | } 24 | 25 | div.related ~ div.related { 26 | border-top: 1px solid #ccc; 27 | border-bottom: none; 28 | } 29 | 30 | .related .switchers { 31 | display: inline-flex; 32 | } 33 | 34 | .switchers > div { 35 | margin-right: 5px; 36 | } 37 | 38 | .version_switcher_placeholder, 39 | .language_switcher_placeholder { 40 | padding-left: 5px; 41 | background-color: white; 42 | } 43 | 44 | .inline-search { 45 | display: inline; 46 | } 47 | form.inline-search input { 48 | display: inline; 49 | } 50 | form.inline-search input[type="submit"] { 51 | width: 40px; 52 | } 53 | 54 | div.sphinxsidebar { 55 | background-color: #eeeeee; 56 | border-radius: 5px; 57 | line-height: 130%; 58 | font-size: smaller; 59 | } 60 | 61 | div.sphinxsidebar h3, div.sphinxsidebar h4 { 62 | margin-top: 1.5em; 63 | } 64 | 65 | div.sphinxsidebarwrapper > h3:first-child { 66 | margin-top: 0.2em; 67 | } 68 | 69 | div.sphinxsidebarwrapper > ul > li > ul > li { 70 | margin-bottom: 0.4em; 71 | } 72 | 73 | div.sphinxsidebar a:hover { 74 | color: #0095C4; 75 | } 76 | 77 | form.inline-search input, 78 | div.sphinxsidebar input { 79 | font-family: 'Lucida Grande',Arial,sans-serif; 80 | border: 1px solid #999999; 81 | font-size: smaller; 82 | border-radius: 3px; 83 | } 84 | 85 | div.sphinxsidebar input[type=text] { 86 | max-width: 150px; 87 | } 88 | 89 | div.body { 90 | padding: 0 0 0 1.2em; 91 | } 92 | 93 | div.body p { 94 | line-height: 140%; 95 | } 96 | 97 | div.body h1, div.body h2, div.body h3, div.body h4, div.body h5, div.body h6 { 98 | margin: 0; 99 | border: 0; 100 | padding: 0.3em 0; 101 | } 102 | 103 | div.body hr { 104 | border: 0; 105 | background-color: #ccc; 106 | height: 1px; 107 | } 108 | 109 | div.body pre { 110 | border-radius: 3px; 111 | border: 1px solid #ac9; 112 | } 113 | 114 | div.body div.admonition, div.body div.impl-detail { 115 | border-radius: 3px; 116 | } 117 | 118 | div.body div.impl-detail > p { 119 | margin: 0; 120 | } 121 | 122 | div.body div.seealso { 123 | border: 1px solid #dddd66; 124 | } 125 | 126 | div.body a { 127 | color: #0072aa; 128 | } 129 | 130 | div.body a:visited { 131 | color: #6363bb; 132 | } 133 | 134 | div.body a:hover { 135 | color: #00B0E4; 136 | } 137 | 138 | tt, code, pre { 139 | font-family: monospace, sans-serif; 140 | font-size: 96.5%; 141 | } 142 | 143 | div.body tt, div.body code { 144 | border-radius: 3px; 145 | } 146 | 147 | div.body tt.descname, div.body code.descname { 148 | font-size: 120%; 149 | } 150 | 151 | div.body tt.xref, div.body a tt, div.body code.xref, div.body a code { 152 | font-weight: normal; 153 | } 154 | 155 | table.docutils { 156 | border: 1px solid #ddd; 157 | min-width: 20%; 158 | border-radius: 3px; 159 | margin-top: 10px; 160 | margin-bottom: 10px; 161 | } 162 | 163 | table.docutils td, table.docutils th { 164 | border: 1px solid #ddd !important; 165 | border-radius: 3px; 166 | } 167 | 168 | table p, table li { 169 | text-align: left !important; 170 | } 171 | 172 | table.docutils th { 173 | background-color: #eee; 174 | padding: 0.3em 0.5em; 175 | } 176 | 177 | table.docutils td { 178 | background-color: white; 179 | padding: 0.3em 0.5em; 180 | } 181 | 182 | table.footnote, table.footnote td { 183 | border: 0 !important; 184 | } 185 | 186 | div.footer { 187 | line-height: 150%; 188 | margin-top: -2em; 189 | text-align: right; 190 | width: auto; 191 | margin-right: 10px; 192 | } 193 | 194 | div.footer a:hover { 195 | color: #0095C4; 196 | } 197 | 198 | .refcount { 199 | color: #060; 200 | } 201 | 202 | .stableabi { 203 | color: #229; 204 | } 205 | 206 | .highlight { 207 | background: none !important; 208 | } 209 | 210 | dl > dt span ~ em { 211 | font-family: monospace, sans-serif; 212 | } 213 | 214 | .toctree-wrapper ul { 215 | padding-left: 20px; 216 | } 217 | 218 | @media (max-width: 1023px) { 219 | /* Body layout */ 220 | div.body { 221 | min-width: 100%; 222 | padding: 0; 223 | font-size: 0.875rem; 224 | } 225 | div.bodywrapper { 226 | margin: 0; 227 | } 228 | /* Typography */ 229 | div.body h1 { 230 | font-size: 1.625rem; 231 | } 232 | div.body h2 { 233 | font-size: 1.25rem; 234 | } 235 | div.body h3, div.body h4, div.body h5 { 236 | font-size: 1rem; 237 | } 238 | /* Remove sidebar and top related bar */ 239 | div.related, .sphinxsidebar { 240 | display: none; 241 | } 242 | /* Anchorlinks are not hidden by fixed-positioned navbar when scrolled to */ 243 | html { 244 | scroll-padding-top: 40px; 245 | } 246 | 247 | /* Top navigation bar */ 248 | .mobile-nav { 249 | display: block; 250 | height: 40px; 251 | width: 100%; 252 | position: fixed; 253 | top: 0; 254 | left: 0; 255 | background-color: white; 256 | box-shadow: rgba(0, 0, 0, 0.25) 0 0 2px 0; 257 | z-index: 1; 258 | } 259 | .mobile-nav * { 260 | box-sizing: border-box; 261 | } 262 | .nav-content { 263 | position: absolute; 264 | z-index: 2; 265 | left: 0; 266 | top: 0; 267 | height: 40px; 268 | width: 100%; 269 | padding: 0 1rem 0 45px; 270 | display: flex; 271 | align-items: center; 272 | background-color: white; 273 | } 274 | .nav-logo { 275 | margin-right: 0.7rem; 276 | display: flex; 277 | flex: 0 0 auto; 278 | } 279 | .nav-content img { 280 | width: 20px; 281 | height: auto; 282 | } 283 | .version_switcher_placeholder { 284 | flex: 0 1 0; 285 | margin-right: 1rem; 286 | } 287 | .version_switcher_placeholder select { 288 | height: 30px; 289 | border-radius: 0; 290 | } 291 | .nav-content .search { 292 | display: flex; 293 | flex: 1 1 auto; 294 | align-items: center; 295 | padding: 0 0 0 2px; 296 | border: 1px solid #a9a9a9; 297 | height: 30px; 298 | overflow: hidden; 299 | } 300 | .nav-content .search:hover { 301 | box-shadow: 0 1px 6px 0 rgba(32,33,36,0.28); 302 | border-color: rgba(223,225,229,0); 303 | } 304 | .nav-content .search input[type=text] { 305 | border: 0; 306 | outline: 0; 307 | box-shadow: none; 308 | width: 40px; 309 | height: 28px; 310 | flex: 1 1 auto; 311 | } 312 | .nav-content .search input[type=submit] { 313 | height: 100%; 314 | border: 0; 315 | box-shadow: none; 316 | outline: 1px solid #999; 317 | cursor: pointer; 318 | } 319 | .nav-content .search svg { 320 | flex: 0 0 20px; 321 | fill: #333; 322 | } 323 | .toggler__input { 324 | width: 40px; 325 | height: 40px; 326 | left: 0; 327 | opacity: 0; 328 | position: absolute; 329 | z-index: 3; 330 | margin: 0; 331 | } 332 | .toggler__label { 333 | width: 40px; 334 | height: 40px; 335 | margin: 0; 336 | position: absolute; 337 | cursor: pointer; 338 | top: 0; 339 | left: 0; 340 | background-color: transparent; 341 | border: 1px solid white; 342 | box-shadow: none; 343 | z-index: 3; 344 | display: flex; 345 | align-items: center; 346 | justify-content: center; 347 | padding: 0 8px; 348 | } 349 | .toggler__label:focus { 350 | background-color: #eee; 351 | border: 1px solid #ededed; 352 | box-shadow: rgba(0, 0, 0, 0.25) 1px 0 2px 0; 353 | } 354 | .toggler__label:hover { 355 | background-color: #eee; 356 | border: 1px solid #ededed; 357 | box-shadow: rgba(0, 0, 0, 0.25) 1px 0 2px 0; 358 | } 359 | .toggler__label > span { 360 | position: relative; 361 | flex: none; 362 | height: 2px; 363 | width: 100%; 364 | background: #444; 365 | transition: all 400ms ease; 366 | } 367 | .toggler__label > span::before, 368 | .toggler__label > span::after { 369 | content: ''; 370 | height: 2px; 371 | width: 100%; 372 | background: inherit; 373 | position: absolute; 374 | left: 0; 375 | top: -8px; 376 | } 377 | .toggler__label > span::after { 378 | top: 8px; 379 | } 380 | .toggler__input:checked ~ .toggler__label span { 381 | transform: rotate(135deg); 382 | } 383 | .toggler__input:checked ~ .toggler__label span::before { 384 | transform: rotate(90deg); 385 | } 386 | .toggler__input:checked ~ .toggler__label span::before, 387 | .toggler__input:checked ~ .toggler__label span::after { 388 | top: 0; 389 | } 390 | .toggler__input:checked:hover ~ .toggler__label span { 391 | transform: rotate(315deg); 392 | } 393 | .toggler__input:checked ~ .menu-wrapper { 394 | visibility: visible; 395 | left: 0; 396 | } 397 | 398 | /* Sliding side menu */ 399 | .menu-wrapper { 400 | display: block; 401 | position: fixed; 402 | top: 0; 403 | transition: left 400ms ease; 404 | left: -310px; 405 | width: 300px; 406 | height: 100%; 407 | background-color: #eee; 408 | box-shadow: 0 0 10px rgba(0, 0, 0, 0.2); 409 | overflow-y: auto; 410 | } 411 | .menu-wrapper.open { 412 | visibility: visible; 413 | left: 0; 414 | } 415 | .menu { 416 | padding: 40px 10px 30px 20px; 417 | } 418 | .menu-wrapper h3, 419 | .menu-wrapper h4 { 420 | margin-bottom: 0; 421 | font-weight: normal; 422 | } 423 | .menu-wrapper h4 { 424 | font-size: 1.3em; 425 | } 426 | .menu-wrapper h3 { 427 | color: #444444; 428 | font-size: 1.4em; 429 | } 430 | .menu-wrapper h3 + p, 431 | .menu-wrapper h4 + p { 432 | margin-top: 0.5rem; 433 | } 434 | .menu a { 435 | font-size: smaller; 436 | color: #444444; 437 | text-decoration: none; 438 | } 439 | .menu ul { 440 | list-style: none; 441 | line-height: 1.4; 442 | overflow-wrap: break-word; 443 | padding-left: 0; 444 | } 445 | .menu ul ul { 446 | margin-left: 20px; 447 | list-style: square; 448 | } 449 | .menu ul li { 450 | margin-bottom: 0.5rem; 451 | } 452 | .language_switcher_placeholder, 453 | .version_switcher_placeholder { 454 | position: relative; 455 | border: 1px solid #a8a8a8; 456 | height: 30px; 457 | } 458 | .language_switcher_placeholder { 459 | margin-top: 2rem; 460 | } 461 | .language_switcher_placeholder::after, 462 | .version_switcher_placeholder::after { 463 | content: url('../_static/caret-down.svg'); 464 | position: absolute; 465 | top: 7px; 466 | width: 15px; 467 | height: 15px; 468 | right: 3px; 469 | pointer-events: none; 470 | } 471 | .language_switcher_placeholder select, 472 | .version_switcher_placeholder select { 473 | appearance: none; 474 | border: 0; 475 | height: 100%; 476 | } 477 | .language_switcher_placeholder select { 478 | width: 100%; 479 | } 480 | .document { 481 | padding-top: 40px; 482 | position: relative; 483 | z-index: 0; 484 | } 485 | /*Responsive tables*/ 486 | .responsive-table__container { 487 | width: 100%; 488 | overflow-x: auto; 489 | } 490 | } 491 | -------------------------------------------------------------------------------- /docs/build/html/_static/pygments.css: -------------------------------------------------------------------------------- 1 | pre { line-height: 125%; } 2 | td.linenos .normal { color: #666666; background-color: transparent; padding-left: 5px; padding-right: 5px; } 3 | span.linenos { color: #666666; background-color: transparent; padding-left: 5px; padding-right: 5px; } 4 | td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 5 | span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; } 6 | .highlight .hll { background-color: #ffffcc } 7 | .highlight { background: #f0f0f0; } 8 | .highlight .c { color: #60a0b0; font-style: italic } /* Comment */ 9 | .highlight .err { border: 1px solid #FF0000 } /* Error */ 10 | .highlight .k { color: #007020; font-weight: bold } /* Keyword */ 11 | .highlight .o { color: #666666 } /* Operator */ 12 | .highlight .ch { color: #60a0b0; font-style: italic } /* Comment.Hashbang */ 13 | .highlight .cm { color: #60a0b0; font-style: italic } /* Comment.Multiline */ 14 | .highlight .cp { color: #007020 } /* Comment.Preproc */ 15 | .highlight .cpf { color: #60a0b0; font-style: italic } /* Comment.PreprocFile */ 16 | .highlight .c1 { color: #60a0b0; font-style: italic } /* Comment.Single */ 17 | .highlight .cs { color: #60a0b0; background-color: #fff0f0 } /* Comment.Special */ 18 | .highlight .gd { color: #A00000 } /* Generic.Deleted */ 19 | .highlight .ge { font-style: italic } /* Generic.Emph */ 20 | .highlight .gr { color: #FF0000 } /* Generic.Error */ 21 | .highlight .gh { color: #000080; font-weight: bold } /* Generic.Heading */ 22 | .highlight .gi { color: #00A000 } /* Generic.Inserted */ 23 | .highlight .go { color: #888888 } /* Generic.Output */ 24 | .highlight .gp { color: #c65d09; font-weight: bold } /* Generic.Prompt */ 25 | .highlight .gs { font-weight: bold } /* Generic.Strong */ 26 | .highlight .gu { color: #800080; font-weight: bold } /* Generic.Subheading */ 27 | .highlight .gt { color: #0044DD } /* Generic.Traceback */ 28 | .highlight .kc { color: #007020; font-weight: bold } /* Keyword.Constant */ 29 | .highlight .kd { color: #007020; font-weight: bold } /* Keyword.Declaration */ 30 | .highlight .kn { color: #007020; font-weight: bold } /* Keyword.Namespace */ 31 | .highlight .kp { color: #007020 } /* Keyword.Pseudo */ 32 | .highlight .kr { color: #007020; font-weight: bold } /* Keyword.Reserved */ 33 | .highlight .kt { color: #902000 } /* Keyword.Type */ 34 | .highlight .m { color: #40a070 } /* Literal.Number */ 35 | .highlight .s { color: #4070a0 } /* Literal.String */ 36 | .highlight .na { color: #4070a0 } /* Name.Attribute */ 37 | .highlight .nb { color: #007020 } /* Name.Builtin */ 38 | .highlight .nc { color: #0e84b5; font-weight: bold } /* Name.Class */ 39 | .highlight .no { color: #60add5 } /* Name.Constant */ 40 | .highlight .nd { color: #555555; font-weight: bold } /* Name.Decorator */ 41 | .highlight .ni { color: #d55537; font-weight: bold } /* Name.Entity */ 42 | .highlight .ne { color: #007020 } /* Name.Exception */ 43 | .highlight .nf { color: #06287e } /* Name.Function */ 44 | .highlight .nl { color: #002070; font-weight: bold } /* Name.Label */ 45 | .highlight .nn { color: #0e84b5; font-weight: bold } /* Name.Namespace */ 46 | .highlight .nt { color: #062873; font-weight: bold } /* Name.Tag */ 47 | .highlight .nv { color: #bb60d5 } /* Name.Variable */ 48 | .highlight .ow { color: #007020; font-weight: bold } /* Operator.Word */ 49 | .highlight .w { color: #bbbbbb } /* Text.Whitespace */ 50 | .highlight .mb { color: #40a070 } /* Literal.Number.Bin */ 51 | .highlight .mf { color: #40a070 } /* Literal.Number.Float */ 52 | .highlight .mh { color: #40a070 } /* Literal.Number.Hex */ 53 | .highlight .mi { color: #40a070 } /* Literal.Number.Integer */ 54 | .highlight .mo { color: #40a070 } /* Literal.Number.Oct */ 55 | .highlight .sa { color: #4070a0 } /* Literal.String.Affix */ 56 | .highlight .sb { color: #4070a0 } /* Literal.String.Backtick */ 57 | .highlight .sc { color: #4070a0 } /* Literal.String.Char */ 58 | .highlight .dl { color: #4070a0 } /* Literal.String.Delimiter */ 59 | .highlight .sd { color: #4070a0; font-style: italic } /* Literal.String.Doc */ 60 | .highlight .s2 { color: #4070a0 } /* Literal.String.Double */ 61 | .highlight .se { color: #4070a0; font-weight: bold } /* Literal.String.Escape */ 62 | .highlight .sh { color: #4070a0 } /* Literal.String.Heredoc */ 63 | .highlight .si { color: #70a0d0; font-style: italic } /* Literal.String.Interpol */ 64 | .highlight .sx { color: #c65d09 } /* Literal.String.Other */ 65 | .highlight .sr { color: #235388 } /* Literal.String.Regex */ 66 | .highlight .s1 { color: #4070a0 } /* Literal.String.Single */ 67 | .highlight .ss { color: #517918 } /* Literal.String.Symbol */ 68 | .highlight .bp { color: #007020 } /* Name.Builtin.Pseudo */ 69 | .highlight .fm { color: #06287e } /* Name.Function.Magic */ 70 | .highlight .vc { color: #bb60d5 } /* Name.Variable.Class */ 71 | .highlight .vg { color: #bb60d5 } /* Name.Variable.Global */ 72 | .highlight .vi { color: #bb60d5 } /* Name.Variable.Instance */ 73 | .highlight .vm { color: #bb60d5 } /* Name.Variable.Magic */ 74 | .highlight .il { color: #40a070 } /* Literal.Number.Integer.Long */ -------------------------------------------------------------------------------- /docs/build/html/_static/sidebar.js: -------------------------------------------------------------------------------- 1 | /* 2 | * sidebar.js 3 | * ~~~~~~~~~~ 4 | * 5 | * This script makes the Sphinx sidebar collapsible and implements intelligent 6 | * scrolling. This is a slightly modified version of Sphinx's own sidebar.js. 7 | * 8 | * .sphinxsidebar contains .sphinxsidebarwrapper. This script adds in 9 | * .sphixsidebar, after .sphinxsidebarwrapper, the #sidebarbutton used to 10 | * collapse and expand the sidebar. 11 | * 12 | * When the sidebar is collapsed the .sphinxsidebarwrapper is hidden and the 13 | * width of the sidebar and the margin-left of the document are decreased. 14 | * When the sidebar is expanded the opposite happens. This script saves a 15 | * per-browser/per-session cookie used to remember the position of the sidebar 16 | * among the pages. Once the browser is closed the cookie is deleted and the 17 | * position reset to the default (expanded). 18 | * 19 | * :copyright: Copyright 2007-2011 by the Sphinx team, see AUTHORS. 20 | * :license: BSD, see LICENSE for details. 21 | * 22 | */ 23 | 24 | $(function() { 25 | // global elements used by the functions. 26 | // the 'sidebarbutton' element is defined as global after its 27 | // creation, in the add_sidebar_button function 28 | var jwindow = $(window); 29 | var jdocument = $(document); 30 | var bodywrapper = $('.bodywrapper'); 31 | var documentwrapper = $('.documentwrapper'); 32 | var sidebar = $('.sphinxsidebar'); 33 | var sidebarwrapper = $('.sphinxsidebarwrapper'); 34 | 35 | // original margin-left of the bodywrapper and width of the sidebar 36 | // with the sidebar expanded 37 | var bw_margin_expanded = bodywrapper.css('margin-left'); 38 | var ssb_width_expanded = sidebar.width(); 39 | 40 | // margin-left of the bodywrapper and width of the sidebar 41 | // with the sidebar collapsed 42 | var bw_margin_collapsed = '.8em'; 43 | var ssb_width_collapsed = '.8em'; 44 | 45 | // colors used by the current theme 46 | var dark_color = '#AAAAAA'; 47 | var light_color = '#CCCCCC'; 48 | 49 | function get_viewport_height() { 50 | if (window.innerHeight) 51 | return window.innerHeight; 52 | else 53 | return jwindow.height(); 54 | } 55 | 56 | function sidebar_is_collapsed() { 57 | return sidebarwrapper.is(':not(:visible)'); 58 | } 59 | 60 | function toggle_sidebar() { 61 | if (sidebar_is_collapsed()) 62 | expand_sidebar(); 63 | else 64 | collapse_sidebar(); 65 | // adjust the scrolling of the sidebar 66 | scroll_sidebar(); 67 | } 68 | 69 | function collapse_sidebar() { 70 | sidebarwrapper.hide(); 71 | sidebar.css('width', ssb_width_collapsed); 72 | bodywrapper.css('margin-left', bw_margin_collapsed); 73 | sidebarbutton.css({ 74 | 'margin-left': '0', 75 | 'height': documentwrapper.height(), 76 | 'border-radius': '5px' 77 | }); 78 | sidebarbutton.find('span').text('»'); 79 | sidebarbutton.attr('title', _('Expand sidebar')); 80 | document.cookie = 'sidebar=collapsed'; 81 | } 82 | 83 | function expand_sidebar() { 84 | bodywrapper.css('margin-left', bw_margin_expanded); 85 | sidebar.css('width', ssb_width_expanded); 86 | sidebarwrapper.show(); 87 | sidebarbutton.css({ 88 | 'margin-left': ssb_width_expanded-12, 89 | 'height': Math.max(sidebarwrapper.height(), documentwrapper.height()), 90 | 'border-radius': '0 5px 5px 0' 91 | }); 92 | sidebarbutton.find('span').text('«'); 93 | sidebarbutton.attr('title', _('Collapse sidebar')); 94 | //sidebarwrapper.css({'padding-top': 95 | // Math.max(window.pageYOffset - sidebarwrapper.offset().top, 10)}); 96 | document.cookie = 'sidebar=expanded'; 97 | } 98 | 99 | function add_sidebar_button() { 100 | sidebarwrapper.css({ 101 | 'float': 'left', 102 | 'margin-right': '0', 103 | 'width': ssb_width_expanded - 28 104 | }); 105 | // create the button 106 | sidebar.append( 107 | '
«
' 108 | ); 109 | var sidebarbutton = $('#sidebarbutton'); 110 | // find the height of the viewport to center the '<<' in the page 111 | var viewport_height = get_viewport_height(); 112 | var sidebar_offset = sidebar.offset().top; 113 | var sidebar_height = Math.max(documentwrapper.height(), sidebar.height()); 114 | sidebarbutton.find('span').css({ 115 | 'display': 'block', 116 | 'position': 'fixed', 117 | 'top': Math.min(viewport_height/2, sidebar_height/2 + sidebar_offset) - 10 118 | }); 119 | 120 | sidebarbutton.click(toggle_sidebar); 121 | sidebarbutton.attr('title', _('Collapse sidebar')); 122 | sidebarbutton.css({ 123 | 'border-radius': '0 5px 5px 0', 124 | 'color': '#444444', 125 | 'background-color': '#CCCCCC', 126 | 'font-size': '1.2em', 127 | 'cursor': 'pointer', 128 | 'height': sidebar_height, 129 | 'padding-top': '1px', 130 | 'padding-left': '1px', 131 | 'margin-left': ssb_width_expanded - 12 132 | }); 133 | 134 | sidebarbutton.hover( 135 | function () { 136 | $(this).css('background-color', dark_color); 137 | }, 138 | function () { 139 | $(this).css('background-color', light_color); 140 | } 141 | ); 142 | } 143 | 144 | function set_position_from_cookie() { 145 | if (!document.cookie) 146 | return; 147 | var items = document.cookie.split(';'); 148 | for(var k=0; k wintop && curbot > winbot) { 185 | sidebarwrapper.css('top', $u.max([wintop - offset - 10, 0])); 186 | } 187 | else if (curtop < wintop && curbot < winbot) { 188 | sidebarwrapper.css('top', $u.min([winbot - sidebar_height - offset - 20, 189 | jdocument.height() - sidebar_height - 200])); 190 | } 191 | } 192 | } 193 | jwindow.scroll(scroll_sidebar); 194 | }); 195 | -------------------------------------------------------------------------------- /docs/build/html/exceptions.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | python-powerdns – Exceptions — python-powerdns 2.0.0 10 | documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 |
29 | 31 | 34 | 48 | 76 |
77 | 78 | 122 | 123 |
124 |
125 |
126 |
127 | 128 |
129 |

python-powerdns – Exceptions

130 |
131 |
132 |
133 | class powerdns.exceptions.PDNSCanonicalError(name)[source]
134 |

PowerDNS Canonical Error

135 |
136 | 137 |
138 |
139 | class powerdns.exceptions.PDNSError(url, status_code, message)[source]
140 |

PowerDNS API Exception

141 |
142 | 143 |
144 |
145 | 146 | 147 |
148 |
149 |
150 |
151 | 178 |
179 |
180 | 224 | 245 | 246 | 247 | -------------------------------------------------------------------------------- /docs/build/html/genindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Index — python-powerdns 2.0.0 9 | documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 28 | 31 | 45 | 60 |
61 | 62 | 100 | 101 |
102 |
103 |
104 |
105 | 106 | 107 |

Index

108 | 109 |
110 | _ 111 | | B 112 | | C 113 | | D 114 | | E 115 | | G 116 | | M 117 | | N 118 | | P 119 | | R 120 | | S 121 | | Z 122 | 123 |
124 |

_

125 | 126 | 130 |
131 | 132 |

B

133 | 134 | 138 | 142 |
143 | 144 |

C

145 | 146 | 152 | 158 |
159 | 160 |

D

161 | 162 | 168 | 174 |
175 | 176 |

E

177 | 178 | 182 |
183 | 184 |

G

185 | 186 | 190 | 196 |
197 | 198 |

M

199 | 200 | 209 |
    201 |
  • 202 | module 203 | 204 |
  • 208 |
210 | 211 |

N

212 | 213 | 217 |
218 | 219 |

P

220 | 221 | 235 | 252 |
253 | 254 |

R

255 | 256 | 262 | 268 |
269 | 270 |

S

271 | 272 | 276 | 282 |
283 | 284 |

Z

285 | 286 | 290 |
291 | 292 | 293 | 294 |
295 |
296 |
297 |
298 | 312 |
313 |
314 | 352 | 373 | 374 | 375 | -------------------------------------------------------------------------------- /docs/build/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | python-powerdns – PowerDNS web api python client and interface — python-powerdns 2.0.0 10 | documentation 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 |
28 | 30 | 33 | 47 | 72 |
73 | 74 | 115 | 116 |
117 |
118 |
119 |
120 | 121 |
122 |

python-powerdns – PowerDNS web api python client and interface

123 |

The powerdns package provides an intuitive and easy to use WEB API of 124 | PowerDNS admin. It defines the following attribute:

125 |
126 |
127 |
128 | powerdns.__version__ = '2.0.0'
129 |

Current version of the package as str.

130 |
131 | 132 |
133 |
134 | powerdns.basic_logger(name=None, clevel=2, slevel=1)[source]
135 |

Configure a basic logger

136 |
137 |
Parameters
138 |
    139 |
  • name (str) – Logger name

  • 140 |
  • clevel (int) – Console log level

  • 141 |
  • slevel (int) – Syslog log level

  • 142 |
143 |
144 |
Returns
145 |

Logger object

146 |
147 |
148 |
149 | 150 |
151 | 158 |
159 | 160 | 161 |
162 |
163 |
164 |
165 | 189 |
190 |
191 | 232 | 253 | 254 | 255 | -------------------------------------------------------------------------------- /docs/build/html/objects.inv: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/outini/python-powerdns/8caa8e283dca8609e91dcc67785366bb412d527f/docs/build/html/objects.inv -------------------------------------------------------------------------------- /docs/build/html/py-modindex.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Python Module Index — python-powerdns 2.0.0 9 | documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 28 | 29 | 30 | 31 | 32 |
33 | 35 | 38 | 52 | 67 |
68 | 69 | 107 | 108 |
109 |
110 |
111 |
112 | 113 | 114 |

Python Module Index

115 | 116 |
117 | p 118 |
119 | 120 | 121 | 122 | 124 | 125 | 126 | 129 |
 
123 | p
127 | powerdns 128 | PowerDNS web api python client and interface.
130 | 131 | 132 |
133 |
134 |
135 |
136 | 150 |
151 |
152 | 190 | 211 | 212 | 213 | -------------------------------------------------------------------------------- /docs/build/html/search.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | Search — python-powerdns 2.0.0 9 | documentation 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 |
32 | 34 | 37 | 43 | 48 |
49 | 50 | 75 | 76 |
77 |
78 |
79 |
80 | 81 |

Search

82 | 83 |
84 | 85 |

86 | Please activate JavaScript to enable the search 87 | functionality. 88 |

89 |
90 | 91 | 92 |

93 | Searching for multiple words only shows matches that contain 94 | all words. 95 |

96 | 97 | 98 |
99 | 100 | 101 | 102 |
103 | 104 | 105 | 106 |
107 | 108 |
109 | 110 | 111 |
112 |
113 |
114 |
115 | 119 |
120 |
121 | 146 | 167 | 168 | 169 | -------------------------------------------------------------------------------- /docs/build/html/searchindex.js: -------------------------------------------------------------------------------- 1 | Search.setIndex({docnames:["client","exceptions","index","interface"],envversion:{"sphinx.domains.c":2,"sphinx.domains.changeset":1,"sphinx.domains.citation":1,"sphinx.domains.cpp":3,"sphinx.domains.index":1,"sphinx.domains.javascript":2,"sphinx.domains.math":2,"sphinx.domains.python":3,"sphinx.domains.rst":2,"sphinx.domains.std":2,"sphinx.ext.intersphinx":1,"sphinx.ext.viewcode":1,sphinx:56},filenames:["client.rst","exceptions.rst","index.rst","interface.rst"],objects:{"":{powerdns:[2,0,0,"-"]},"powerdns.client":{PDNSApiClient:[0,3,1,""]},"powerdns.client.PDNSApiClient":{"delete":[0,4,1,""],get:[0,4,1,""],patch:[0,4,1,""],post:[0,4,1,""],put:[0,4,1,""],request:[0,4,1,""]},"powerdns.exceptions":{PDNSCanonicalError:[1,3,1,""],PDNSError:[1,3,1,""]},"powerdns.interface":{Comment:[3,3,1,""],PDNSEndpoint:[3,3,1,""],PDNSEndpointBase:[3,3,1,""],PDNSServer:[3,3,1,""],PDNSZone:[3,3,1,""],RRSet:[3,3,1,""]},"powerdns.interface.PDNSEndpoint":{servers:[3,5,1,""]},"powerdns.interface.PDNSServer":{config:[3,5,1,""],create_zone:[3,4,1,""],delete_zone:[3,4,1,""],get_zone:[3,4,1,""],restore_zone:[3,4,1,""],search:[3,4,1,""],suggest_zone:[3,4,1,""],zones:[3,5,1,""]},"powerdns.interface.PDNSZone":{backup:[3,4,1,""],create_records:[3,4,1,""],delete_records:[3,4,1,""],details:[3,5,1,""],get_record:[3,4,1,""],notify:[3,4,1,""],records:[3,5,1,""]},"powerdns.interface.RRSet":{ensure_canonical:[3,4,1,""]},powerdns:{__version__:[2,1,1,""],basic_logger:[2,2,1,""]}},objnames:{"0":["py","module","Python module"],"1":["py","data","Python data"],"2":["py","function","Python function"],"3":["py","class","Python class"],"4":["py","method","Python method"],"5":["py","property","Python property"]},objtypes:{"0":"py:module","1":"py:data","2":"py:function","3":"py:class","4":"py:method","5":"py:property"},terms:{"0":2,"1":2,"100":3,"2":2,"3600":3,"case":3,"class":[0,1,3],"int":[0,2,3],"new":3,"return":[0,2,3],"true":0,For:3,If:[0,3],In:3,It:[0,2,3],The:2,Will:3,__version__:2,account:3,actual:3,addit:0,admin:2,also:3,an:[0,2,3],anoth:3,api:[0,1,3],api_cli:3,api_data:3,api_endpoint:0,api_kei:0,api_spec:3,apiv1serversserver95idconfig:3,apiv1serversserver95idzon:3,apiv1serversserver95idzoneszone95id:3,ar:[0,3],argument:0,attribut:2,backup:3,base:3,basic:2,basic_logg:2,best:3,bool:[0,3],build:3,cach:3,cachet:3,canon:[1,3],certif:0,changetyp:3,check:3,clevel:2,client:3,cname:3,com:3,comment:3,common:0,config:3,config_set:3,config_url:3,configur:[2,3],consol:2,content:3,content_str:3,control:0,convert:3,creat:3,create_record:3,create_zon:3,creation:3,current:[2,3],daemon_typ:3,data:[0,3],defin:2,delet:[0,3],delete_record:3,delete_zon:3,destin:3,detail:3,dict:[0,3],directli:0,directori:3,disabl:3,disabled_bool:3,displai:3,dn:3,doc:[0,3],domain:3,done:3,dot:3,easi:2,enabl:3,endpoint:[0,3],ensur:3,ensure_canon:3,error:[0,1],everi:3,exampl:3,except:2,exist:3,extens:3,fals:3,file:3,filenam:3,follow:[2,3],forward:3,from:3,gener:3,get:[0,3],get_record:3,get_zon:3,handl:0,here:3,http:[0,3],httpapi:3,id:3,implement:0,inform:0,instanc:3,intuit:2,invok:0,json:[0,3],json_fil:3,kei:[0,3],keyword:3,kind:3,kwarg:0,last:3,level:2,list:3,live:3,localhost:3,log:2,logger:2,longer:3,mai:0,master:3,match:3,max_result:3,md:3,messag:1,method:[0,3],modifi:3,modified_at:3,more:[0,3],name:[0,1,2,3],nameserv:3,need:3,none:[0,2,3],notif:3,notifi:3,object:[0,2,3],object_typ:3,one:3,onli:3,option:[0,3],org:0,packag:[0,2],paramet:[0,2,3],pars:0,partial:0,pass:0,patch:0,path:0,pdnsapicli:[0,3],pdnscanonicalerror:1,pdnsendpoint:3,pdnsendpointbas:3,pdnserror:[0,1],pdnsserver:3,pdnszone:3,pleas:0,possibl:3,post:0,pretti:3,pretty_json:3,produc:3,properti:3,propos:3,provid:[2,3],put:0,queri:3,r_name:3,rais:0,receiv:3,record:3,recursor:3,replac:3,request:0,reset:3,resourc:3,respons:[0,3],restor:3,restore_zon:3,result:3,revert:3,rrset:3,rtype:3,s:[0,3],search:3,search_term:3,second:0,see:0,self:0,send:0,server:3,session:0,set:3,slevel:2,sourc:[0,1,2,3],spec:3,ssl:0,status_cod:1,store:3,str:[0,2,3],strip:3,structur:3,sub:3,suffix:3,suggest:3,suggest_zon:3,syslog:2,term:3,test:3,thi:[0,3],time:3,timeout:0,timestamp:3,tld:3,transmit:0,trigger:3,ttl:3,tupl:3,type:3,unix:3,updat:3,url:[1,3],us:[0,2,3],v1:3,valid:0,valu:3,verifi:0,version:[2,3],wa:3,which:3,zone95collect:3,zone:3,zone_id:3,zoneid:3,zonenam:3,zones_url:3},titles:["python-powerdns \u2013 Client","python-powerdns \u2013 Exceptions","python-powerdns \u2013 PowerDNS web api python client and interface","python-powerdns \u2013 Interface"],titleterms:{api:2,client:[0,2],except:1,interfac:[2,3],powerdn:[0,1,2,3],python:[0,1,2,3],web:2}}) -------------------------------------------------------------------------------- /docs/custom.css: -------------------------------------------------------------------------------- 1 | 2 | @import url("pydoctheme.css"); 3 | 4 | div.body { 5 | min-width: 450px; 6 | max-width: 1200px; 7 | } 8 | -------------------------------------------------------------------------------- /docs/html: -------------------------------------------------------------------------------- 1 | build/html -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /docs/requirements.txt: -------------------------------------------------------------------------------- 1 | Sphinx 2 | python-docs-theme 3 | -------------------------------------------------------------------------------- /docs/source/client.rst: -------------------------------------------------------------------------------- 1 | python-powerdns -- Client 2 | ========================= 3 | 4 | .. autoclass:: powerdns.client.PDNSApiClient 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/source/conf.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | 4 | import os 5 | import sys 6 | 7 | sys.path.insert(0, os.path.abspath(os.path.join('..', '..'))) 8 | extensions = [ 9 | 'sphinx.ext.autodoc', 10 | 'sphinx.ext.doctest', 11 | 'sphinx.ext.intersphinx', 12 | 'sphinx.ext.coverage', 13 | 'sphinx.ext.viewcode' 14 | ] 15 | templates_path = ['_templates'] 16 | source_suffix = '.rst' 17 | master_doc = 'index' 18 | 19 | project = 'python-powerdns' 20 | copyright = '2018, Denis \'jawa\' Pompilio' 21 | author = 'Denis \'jawa\' Pompilio' 22 | 23 | 24 | version_file = os.path.join(os.path.dirname(__file__), "..", "..", "VERSION") 25 | release = open(version_file).read() 26 | version = ".".join(release.split('.')[0:2]) 27 | 28 | language = None 29 | 30 | exclude_patterns = [] 31 | pygments_style = 'friendly' 32 | todo_include_todos = False 33 | html_theme = "python_docs_theme" 34 | html_style = 'custom.css' 35 | htmlhelp_basename = 'python-powerdns-doc' 36 | 37 | intersphinx_mapping = {'https://docs.python.org/3/': None, 38 | 'http://docs.python-requests.org/en/master/': None} 39 | -------------------------------------------------------------------------------- /docs/source/exceptions.rst: -------------------------------------------------------------------------------- 1 | python-powerdns -- Exceptions 2 | ============================= 3 | 4 | .. autoclass:: powerdns.exceptions.PDNSCanonicalError 5 | :members: 6 | 7 | .. autoclass:: powerdns.exceptions.PDNSError 8 | :members: 9 | -------------------------------------------------------------------------------- /docs/source/index.rst: -------------------------------------------------------------------------------- 1 | python-powerdns -- PowerDNS web api python client and interface 2 | =============================================================== 3 | 4 | .. module:: powerdns 5 | :synopsis: PowerDNS web api python client and interface. 6 | .. moduleauthor:: Denis 'jawa' Pompilio 7 | .. sectionauthor:: Denis 'jawa' Pompilio 8 | 9 | 10 | The :mod:`powerdns` package provides an intuitive and easy to use WEB API of 11 | PowerDNS admin. It defines the following attribute: 12 | 13 | .. autodata:: __version__ 14 | 15 | .. autofunction:: basic_logger 16 | 17 | .. toctree:: 18 | :maxdepth: 1 19 | 20 | exceptions 21 | client 22 | interface 23 | -------------------------------------------------------------------------------- /docs/source/interface.rst: -------------------------------------------------------------------------------- 1 | python-powerdns -- Interface 2 | ============================ 3 | 4 | .. autoclass:: powerdns.interface.PDNSEndpointBase 5 | :members: 6 | 7 | .. autoclass:: powerdns.interface.PDNSEndpoint 8 | :members: 9 | 10 | .. autoclass:: powerdns.interface.PDNSServer 11 | :members: 12 | 13 | .. autoclass:: powerdns.interface.PDNSZone 14 | :members: 15 | 16 | .. autoclass:: powerdns.interface.RRSet 17 | :members: 18 | 19 | .. autoclass:: powerdns.interface.Comment 20 | :members: 21 | -------------------------------------------------------------------------------- /files/Dockerfile: -------------------------------------------------------------------------------- 1 | # syntax=docker/dockerfile:1 2 | FROM debian:latest 3 | RUN apt-get update 4 | RUN apt-get install -y pdns-server pdns-backend-sqlite3 sqlite3 5 | 6 | ADD pdns.conf /pdns/pdns.conf 7 | 8 | # prepare the pdns sqlite3 database 9 | RUN sqlite3 /pdns/pdns.sqlite3 6 | # 7 | # This file is part of python-powerdns 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the MIT License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # MIT License for more details. 16 | # 17 | # You should have received a copy of the MIT License along with this 18 | # program; if not, see . 19 | 20 | """ 21 | powerdns - PowerDNS API client and interface 22 | """ 23 | 24 | import logging 25 | from logging.handlers import SysLogHandler 26 | from .client import PDNSApiClient 27 | from .interface import PDNSEndpoint, RRSet, Comment 28 | 29 | 30 | #: Current version of the package as :class:`str`. 31 | __version__ = "2.1.0" 32 | 33 | LOG_LEVELS = [ 34 | logging.ERROR, 35 | logging.WARN, 36 | logging.INFO, 37 | logging.DEBUG 38 | ] 39 | 40 | 41 | def basic_logger(name=None, clevel=2, slevel=1): 42 | """Configure a basic logger 43 | 44 | :param str name: Logger name 45 | :param int clevel: Console log level 46 | :param int slevel: Syslog log level 47 | :return: Logger object 48 | """ 49 | logger = logging.getLogger(name) 50 | logger.setLevel(LOG_LEVELS[clevel]) 51 | fmt_syslog = logging.Formatter('%(name)s %(levelname)s: %(message)s') 52 | fmt_stream = logging.Formatter('%(name)s %(levelname)s: %(message)s') 53 | stream_handler = logging.StreamHandler() 54 | stream_handler.setFormatter(fmt_stream) 55 | logger.addHandler(stream_handler) 56 | syslog_handler = SysLogHandler(address='/dev/log') 57 | syslog_handler.setFormatter(fmt_syslog) 58 | syslog_handler.setLevel(LOG_LEVELS[slevel]) 59 | logger.addHandler(syslog_handler) 60 | return logger 61 | -------------------------------------------------------------------------------- /powerdns/client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PowerDNS web api python client and interface (python-powerdns) 4 | # 5 | # Copyright (C) 2018 Denis Pompilio (jawa) 6 | # 7 | # This file is part of python-powerdns 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the MIT License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # MIT License for more details. 16 | # 17 | # You should have received a copy of the MIT License along with this 18 | # program; if not, see . 19 | 20 | """ 21 | powerdns.client - PowerDNS API client 22 | """ 23 | 24 | import json 25 | import logging 26 | from functools import partial 27 | import requests 28 | from .exceptions import PDNSError 29 | 30 | 31 | LOG = logging.getLogger(__name__) 32 | 33 | 34 | # pylint: disable=too-many-instance-attributes 35 | class PDNSApiClient(object): 36 | """Powerdns API client 37 | 38 | It implements common HTTP methods GET, POST, PUT, PATCH and DELETE 39 | 40 | This client is using :mod:`requests` package. Please see 41 | http://docs.python-requests.org/ for more information. 42 | 43 | :param str api_endpoint: Powerdns API endpoint 44 | :param str api_key: API key 45 | :param bool verify: Control SSL certificate validation 46 | :param int timeout: Request timeout in seconds 47 | 48 | .. method:: get(self, path, data=None, **kwargs) 49 | 50 | Partial method invoking :meth:`~PDNSApiClient.request` with 51 | http method *GET*. 52 | 53 | .. method:: post(self, path, data=None, **kwargs) 54 | 55 | Partial method invoking :meth:`~PDNSApiClient.request` with 56 | http method *POST*. 57 | 58 | .. method:: put(self, path, data=None, **kwargs) 59 | 60 | Partial method invoking :meth:`~PDNSApiClient.request` with 61 | http method *PUT*. 62 | 63 | .. method:: patch(self, path, data=None, **kwargs) 64 | 65 | Partial method invoking :meth:`~PDNSApiClient.request` with 66 | http method *PATCH*. 67 | 68 | .. method:: delete(self, path, data=None, **kwargs) 69 | 70 | Partial method invoking :meth:`~PDNSApiClient.request` with 71 | http method *DELETE*. 72 | """ 73 | def __init__(self, api_endpoint, api_key, verify=True, timeout=None): 74 | """Initialization""" 75 | self._api_endpoint = api_endpoint 76 | self._api_key = api_key 77 | self._verify = verify 78 | self._timeout = timeout 79 | 80 | if not verify: 81 | LOG.debug("removing insecure https connection warnings") 82 | requests.urllib3.disable_warnings( 83 | requests.urllib3.exceptions.InsecureRequestWarning 84 | ) 85 | 86 | self.request_headers = { 87 | 'Content-Type': 'application/json', 88 | 'Accept': 'application/json' 89 | } 90 | 91 | # Directly expose common HTTP methods 92 | self.get = partial(self.request, method='GET') 93 | self.post = partial(self.request, method='POST') 94 | self.put = partial(self.request, method='PUT') 95 | self.patch = partial(self.request, method='PATCH') 96 | self.delete = partial(self.request, method='DELETE') 97 | 98 | def __repr__(self): 99 | return "PDNSApiClient(%s, %s, verify=%s, timeout=%s)" % ( 100 | repr(self._api_endpoint), repr(self._api_key), 101 | repr(self._verify), repr(self._timeout) 102 | ) 103 | 104 | def __str__(self): 105 | return self._api_endpoint 106 | 107 | def request(self, path, method, data=None, **kwargs): 108 | """Handle requests to API 109 | 110 | :param str path: API endpoint's path to request 111 | :param str method: HTTP method to use 112 | :param dict data: Data to send (optional) 113 | :return: Parsed json response as :class:`dict` 114 | 115 | Additional named argument may be passed and are directly transmitted 116 | to :meth:`request` method of :class:`requests.Session` object. 117 | 118 | :raise PDNSError: If request's response is an error. 119 | """ 120 | if self._api_key: 121 | self.request_headers['X-API-Key'] = self._api_key 122 | 123 | LOG.debug("request: original path is %s", path) 124 | if not path.startswith('http://') and not path.startswith('https://'): 125 | if path.startswith('/'): 126 | path = path.lstrip('/') 127 | url = "%s/%s" % (self._api_endpoint, path) 128 | else: 129 | url = path 130 | 131 | if data is None: 132 | data = {} 133 | data = json.dumps(data) 134 | 135 | LOG.info("request: %s %s", method, url) 136 | LOG.debug("headers: %s", self.request_headers) 137 | LOG.debug("data: %s", data) 138 | response = requests.request(method, url, 139 | data=data, 140 | headers=self.request_headers, 141 | timeout=self._timeout, 142 | verify=self._verify, 143 | **kwargs) 144 | 145 | LOG.info("request response code: %d", response.status_code) 146 | LOG.debug("response: %s", response.text) 147 | 148 | # Try to handle basic return 149 | # pylint: disable=no-else-return 150 | if response.status_code in [200, 201]: 151 | return response.json() 152 | elif response.status_code == 204: 153 | return "" 154 | elif response.status_code == 404: 155 | error_message = 'Not found' 156 | else: 157 | try: 158 | error_message = self._get_error(response=response.json()) 159 | except Exception: 160 | error_message = response.text 161 | 162 | LOG.error("raising error code %d", response.status_code) 163 | LOG.debug("error response: %s", error_message) 164 | raise PDNSError(url=response.url, 165 | status_code=response.status_code, 166 | message=error_message) 167 | 168 | @staticmethod 169 | def _get_error(response): 170 | """Get error message from API response 171 | 172 | :param dict response: API response 173 | :return: Error message as :func:`str` 174 | """ 175 | if 'error' in response: 176 | err = response.get('error') 177 | elif 'errors' in response: 178 | err = response.get('errors') 179 | else: 180 | err = 'No error message found' 181 | return err 182 | -------------------------------------------------------------------------------- /powerdns/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PowerDNS web api python client and interface (python-powerdns) 4 | # 5 | # Copyright (C) 2018 Denis Pompilio (jawa) 6 | # 7 | # This file is part of python-powerdns 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the MIT License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # MIT License for more details. 16 | # 17 | # You should have received a copy of the MIT License along with this 18 | # program; if not, see . 19 | 20 | """ 21 | powerdns.exceptions - PowerDNS API interface exceptions 22 | """ 23 | 24 | 25 | class PDNSCanonicalError(SyntaxError): 26 | """PowerDNS Canonical Error 27 | """ 28 | def __init__(self, name): 29 | """Initialization""" 30 | super(PDNSCanonicalError, self).__init__(name) 31 | self.name = name 32 | self.message = "'%s' is not canonical" % name 33 | 34 | 35 | class PDNSError(Exception): 36 | """PowerDNS API Exception 37 | """ 38 | def __str__(self): 39 | return "code=%d %s: %s" % (self.status_code, self.url, self.message) 40 | 41 | def __repr__(self): 42 | return "PDNSError(\"%s\", %d, \"%s\")" % (self.url, 43 | self.status_code, 44 | self.message) 45 | 46 | def __init__(self, url, status_code, message): 47 | """Initialization""" 48 | super(PDNSError, self).__init__() 49 | self.url = url 50 | self.status_code = status_code 51 | self.message = message 52 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | requests>=2 2 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PowerDNS web api python client and interface (python-powerdns) 4 | # 5 | # Copyright (C) 2018 Denis Pompilio (jawa) 6 | # 7 | # This file is part of python-powerdns 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the MIT License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # MIT License for more details. 16 | # 17 | # You should have received a copy of the MIT License along with this 18 | # program; if not, see . 19 | 20 | import os 21 | import setuptools 22 | 23 | 24 | if __name__ == '__main__': 25 | readme_file = os.path.join(os.path.dirname(__file__), 'README.md') 26 | release = "2.1.0" 27 | setuptools.setup( 28 | name="python-powerdns", 29 | version=release, 30 | url="https://github.com/outini/python-powerdns", 31 | author="Denis Pompilio (jawa)", 32 | author_email="denis.pompilio@gmail.com", 33 | maintainer="Denis Pompilio (jawa)", 34 | maintainer_email="denis.pompilio@gmail.com", 35 | description="PowerDNS web api python client and interface", 36 | long_description=open(readme_file).read(), 37 | long_description_content_type='text/markdown', 38 | license="MIT", 39 | platforms=['UNIX'], 40 | scripts=['bin/pdns-create-zone'], 41 | packages=['powerdns'], 42 | package_dir={'powerdns': 'powerdns'}, 43 | data_files=[ 44 | ('share/doc/python-powerdns', ['README.md', 'LICENSE', 'CHANGES']), 45 | ], 46 | keywords=['dns', 'powerdns', 'api'], 47 | classifiers=[ 48 | 'Development Status :: 5 - Production/Stable', 49 | 'Operating System :: POSIX :: BSD', 50 | 'Operating System :: POSIX :: Linux', 51 | 'License :: OSI Approved :: MIT License', 52 | 'Programming Language :: Python :: 2.7', 53 | 'Programming Language :: Python :: 3.4', 54 | 'Programming Language :: Python :: 3.5', 55 | 'Programming Language :: Python :: 3.6', 56 | 'Programming Language :: Python :: 3.7', 57 | 'Programming Language :: Python :: 3.8', 58 | 'Programming Language :: Python :: 3.9', 59 | 'Environment :: Web Environment', 60 | 'Topic :: Utilities', 61 | ], 62 | requires=['urllib3', 'requests'] 63 | ) 64 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PowerDNS web api python client and interface (python-powerdns) 4 | # 5 | # Copyright (C) 2018 Denis Pompilio (jawa) 6 | # 7 | # This file is part of python-powerdns 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the MIT License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # MIT License for more details. 16 | # 17 | # You should have received a copy of the MIT License along with this 18 | # program; if not, see . 19 | 20 | import powerdns 21 | 22 | from logging import Logger 23 | from unittest import TestCase 24 | 25 | 26 | PDNS_KEY = "MySupErS3cureK3y" 27 | PDNS_API = "http://172.17.0.2:8081/api/v1" 28 | 29 | API_CLIENT = powerdns.PDNSApiClient(api_endpoint=PDNS_API, 30 | api_key=PDNS_KEY, 31 | verify=False) 32 | 33 | 34 | class TestLogger(TestCase): 35 | 36 | def test_logger_creation(self): 37 | self.assertIsInstance(powerdns.basic_logger("test", 2, 1), Logger) 38 | -------------------------------------------------------------------------------- /tests/requirements.txt: -------------------------------------------------------------------------------- 1 | coverage 2 | -------------------------------------------------------------------------------- /tests/test_client.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PowerDNS web api python client and interface (python-powerdns) 4 | # 5 | # Copyright (C) 2018 Denis Pompilio (jawa) 6 | # 7 | # This file is part of python-powerdns 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the MIT License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # MIT License for more details. 16 | # 17 | # You should have received a copy of the MIT License along with this 18 | # program; if not, see . 19 | 20 | from unittest import TestCase 21 | 22 | from . import API_CLIENT, PDNS_API, PDNS_KEY 23 | 24 | 25 | class TestClient(TestCase): 26 | 27 | def test_client_repr_and_str(self): 28 | repr_str = "PDNSApiClient('%s', '%s', verify=False, timeout=None)" % ( 29 | PDNS_API, PDNS_KEY 30 | ) 31 | self.assertEqual(repr(API_CLIENT), repr_str) 32 | self.assertEqual(str(API_CLIENT), PDNS_API) 33 | 34 | def test_client_full_uri(self): 35 | self.assertIsInstance(API_CLIENT.get(PDNS_API + "/servers"), list) 36 | -------------------------------------------------------------------------------- /tests/test_exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PowerDNS web api python client and interface (python-powerdns) 4 | # 5 | # Copyright (C) 2018 Denis Pompilio (jawa) 6 | # 7 | # This file is part of python-powerdns 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the MIT License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # MIT License for more details. 16 | # 17 | # You should have received a copy of the MIT License along with this 18 | # program; if not, see . 19 | 20 | from unittest import TestCase 21 | from powerdns import exceptions 22 | 23 | 24 | class TestExceptions(TestCase): 25 | 26 | def test_exception_pdns_error(self): 27 | exc = exceptions.PDNSError("/fake-url", 404, "Not found.") 28 | self.assertEqual(exc.url, "/fake-url") 29 | self.assertEqual(exc.status_code, 404) 30 | self.assertEqual(exc.message, "Not found.") 31 | self.assertEqual(repr(exc), 'PDNSError("/fake-url", 404, "Not found.")') 32 | self.assertEqual(str(exc), 'code=404 /fake-url: Not found.') 33 | 34 | def test_exception_pdns_canonical_error(self): 35 | self.assertTrue(issubclass(exceptions.PDNSCanonicalError, SyntaxError)) 36 | exc = exceptions.PDNSCanonicalError("fake-name.tld") 37 | self.assertEqual(repr(exc), "PDNSCanonicalError('fake-name.tld')") 38 | self.assertEqual(str(exc), "fake-name.tld") 39 | self.assertEqual(exc.name, "fake-name.tld") 40 | self.assertEqual(exc.message, "'fake-name.tld' is not canonical") 41 | -------------------------------------------------------------------------------- /tests/test_interface.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # PowerDNS web api python client and interface (python-powerdns) 4 | # 5 | # Copyright (C) 2018 Denis Pompilio (jawa) 6 | # 7 | # This file is part of python-powerdns 8 | # 9 | # This program is free software; you can redistribute it and/or 10 | # modify it under the terms of the MIT License. 11 | # 12 | # This program is distributed in the hope that it will be useful, 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 15 | # MIT License for more details. 16 | # 17 | # You should have received a copy of the MIT License along with this 18 | # program; if not, see . 19 | 20 | from datetime import date 21 | from unittest import TestCase 22 | 23 | from powerdns.client import PDNSApiClient 24 | from powerdns.interface import PDNSEndpoint, PDNSServer, PDNSZone 25 | from powerdns.interface import RRSet, Comment 26 | from powerdns.exceptions import PDNSError 27 | 28 | from . import API_CLIENT 29 | 30 | 31 | API = PDNSEndpoint(API_CLIENT) 32 | SERVER = API.servers[0] 33 | ZONE = API.servers[0].get_zone("test.outini.net.") 34 | 35 | 36 | class TestEndpoint(TestCase): 37 | 38 | def test_endpoint_attributes(self): 39 | self.assertIsInstance(API.api_client, PDNSApiClient) 40 | self.assertTrue(hasattr(API, "servers")) 41 | 42 | def test_endpoint_repr_and_str(self): 43 | api_client_repr = repr(API.api_client) 44 | api_repr = "PDNSEndpoint(%s)" % (api_client_repr,) 45 | self.assertEqual(repr(API), api_repr) 46 | self.assertEqual(str(API), str(API.api_client)) 47 | 48 | def test_endpoint_servers_list(self): 49 | self.assertIsInstance(API.servers, list) 50 | self.assertIsInstance(API.servers[0], PDNSServer) 51 | 52 | 53 | class TestServers(TestCase): 54 | 55 | def test_server_object(self): 56 | # server_repr = "PDNSServer(%s, %s)" 57 | # self.assertEqual(repr(SERVER), server_repr) 58 | self.assertEqual(str(SERVER), "localhost") 59 | self.assertEqual(SERVER.sid, "localhost") 60 | self.assertTrue(isinstance(SERVER.version, str)) 61 | 62 | def test_server_config(self): 63 | self.assertIsInstance(SERVER.config, list) 64 | self.assertIsInstance(SERVER.config[0], dict) 65 | 66 | def test_server_zones(self): 67 | self.assertIsInstance(SERVER.zones, list) 68 | self.assertIsInstance(SERVER.zones[0], PDNSZone) 69 | 70 | def test_server_create_zone(self): 71 | zone_name = "test.outini.net." 72 | serial = date.today().strftime("%Y%m%d00") 73 | timers = "28800 7200 604800 86400" 74 | soa = "ns01.test.outini.net. admin.outini.net. %s %s" % (serial, timers) 75 | comments = [Comment("test comment", "admin")] 76 | soa_r = RRSet(name=zone_name, rtype="SOA", ttl=86400, 77 | records=[(soa, False)], comments=comments) 78 | zone_data = dict(name=zone_name, 79 | kind="Master", 80 | rrsets=[soa_r], 81 | nameservers=["ns01.test.outini.net.", 82 | "ns02.test.outini.net."]) 83 | 84 | zone = SERVER.create_zone(**zone_data) 85 | self.assertIsInstance(zone, PDNSZone) 86 | with self.assertRaises(PDNSError): 87 | SERVER.create_zone(**zone_data) 88 | 89 | def test_server_get_zone(self): 90 | self.assertIs(SERVER.get_zone("nonexistent"), None) 91 | self.assertIsInstance(SERVER.get_zone("test.outini.net."), PDNSZone) 92 | 93 | def test_server_suggest_zone(self): 94 | zone = SERVER.suggest_zone("a.b.c.test.outini.net.") 95 | self.assertIsInstance(zone, PDNSZone) 96 | self.assertEqual(zone.name, "test.outini.net.") 97 | 98 | 99 | # class TestZones(TestCase): 100 | # 101 | # def test_zone_object(self): 102 | # # zone_repr = "PDNSZone(%s, %s)" 103 | # # self.assertEqual(repr(ZONE), zone_repr) 104 | # self.assertEqual(str(ZONE), "test.outini.net.") 105 | # print(dir(ZONE)) 106 | 107 | 108 | class TestRRSetRecords(TestCase): 109 | 110 | def test_dict_correct(self): 111 | rrset = RRSet("test", "TXT", [{"content": "foo"}, 112 | {"content": "bar", "disabled": False}, 113 | {"content": "baz", "disabled": True}]) 114 | 115 | self.assertEqual(rrset["records"][0], 116 | {"content": "foo", "disabled": False}) 117 | self.assertEqual(rrset["records"][1], 118 | {"content": "bar", "disabled": False}) 119 | self.assertEqual(rrset["records"][2], 120 | {"content": "baz", "disabled": True}) 121 | 122 | def test_dict_additional_key(self): 123 | with self.assertRaises(ValueError): 124 | RRSet("test", "TXT", [{"content": "baz", 125 | "disabled": False, 126 | "foo": "bar"}]) 127 | 128 | def test_dict_missing_key(self): 129 | with self.assertRaises(ValueError): 130 | RRSet("test", "TXT", [{"content": "baz", 131 | "disabled": False, 132 | "foo": "bar"}]) 133 | --------------------------------------------------------------------------------