diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f377c56 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +venv +.idea diff --git a/README.md b/README.md index 4868bab..9336e13 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,37 @@ # nc_dnsapi -API wrapper for the netcup DNS API +A simple API wrapper for the netcup DNS API + +```python +import nc_dnsapi + +customer = 123456 +api_key = "your-personal-api-key" +api_password = "your-private-api-password" + +with nc_dnsapi.Client(customer, api_key, api_password) as api: + # fetch records + records = api.dns_records("example.com") + print(records) + + # fetch zone details + zone = api.dns_zone("example.com") + print(zone) + + # update single record + api.update_dns_record("example.com", DNSRecord("my-hostname", "A", "127.0.0.2", id=108125)) + + # update list of records + api.update_dns_record("example.com", [ DNSRecord("my-hostname", "A", "127.0.0.2", id=108125), + DNSRecord("my-hostname2", "A", "127.0.0.2", id=108126)]) + + # delete record + api.delete_dns_record("example.com", DNSRecord("my-hostname", "A", "127.0.0.2", id=108125)) + + # add record + api.add_dns_record("example.com", DNSRecord("another-host", "AAAA", "::1")) + + # update zone + zone = api.dns_zone("example.com") + zone.refresh = 3600 + api.update_dns_zone("example.com", zone) +``` diff --git a/nc_dnsapi/__init__.py b/nc_dnsapi/__init__.py new file mode 100644 index 0000000..edb2ff3 --- /dev/null +++ b/nc_dnsapi/__init__.py @@ -0,0 +1,139 @@ +import requests +import json + + +class DNSZone(object): + def __init__(self, name, ttl, serial, refresh, retry, expire, dnssecstatus): + self.name = name + self.ttl = ttl + self.serial = serial + self.refresh = refresh + self.retry = retry + self.expire = expire + self.dnssecstatus = dnssecstatus + + def __str__(self): + return str(self.__dict__) + + +class DNSRecord(object): + __valid_types = ['A', 'AAAA', 'MX', 'CNAME', 'CAA', 'SRV', 'TXT', 'TLSA', 'NS', 'DS'] + + def __init__(self, hostname, record_type, destination, **kwargs): + self.hostname = hostname + self.type = record_type.upper() + self.destination = destination + self.priority = kwargs.get("priority", 0) + self.id = kwargs.get("id", "") + self.deleterecord = kwargs.get("deleterecord", False) + self.state = True + + if self.type not in self.__valid_types: + raise TypeError("Invalid record type: {}".format(self.type)) + + def __str__(self): + return str(self.__dict__) + + +class Client(object): + __endpoint = "https://ccp.netcup.net/run/webservice/servers/endpoint.php?JSON" + __api_session_id = None + + def request(self, action, **kwargs): + params = kwargs.get("params", {}) + params.update({ + "apikey": self.__api_key, + "customernumber": self.__customer, + }) + + if "apipassword" in kwargs: + params.update({kwargs.get("apipassword")}) + + if self.__api_session_id: + params.update({"apisessionid": self.__api_session_id}) + + response = requests.post( + self.__endpoint, + data=json.dumps({ + "action": action, + "param": params + }), + timeout=self.__api_timeout + ) + + if response.ok: + data = response.json() + + if data['status'] == 'success': + return data + else: + raise Exception("{} ({})".format(data['longmessage'], data['statuscode'])) + else: + raise Exception("{} ({})".format(response.reason, response.status_code)) + + def logout(self): + self.request("logout") + + def login(self): + data = self.request("login", params={"apipassword": self.__api_password}) + self.__api_session_id = data['responsedata']['apisessionid'] + + def add_dns_record(self, domain, record: DNSRecord): + self.update_dns_records(domain, [record]) + + def update_dns_record(self, domain, record: DNSRecord): + if not record.id: + raise ValueError("Missing id of record to update") + + self.update_dns_records(domain, [record]) + + def update_dns_records(self, domain, records): + if not all(isinstance(r, DNSRecord) for r in records): + raise TypeError("Record has to be instance of DNSRecord") + + self.request("updateDnsRecords", params={ + "domainname": domain, + "dnsrecordset": {"dnsrecords": [record.__dict__ for record in records]} + }) + + def delete_dns_record(self, domain, record: DNSRecord, ignore_unknown=True): + if not record.id: + raise ValueError("Missing id of record to update") + + record.deleterecord = True + + try: + self.update_dns_records(domain, [record]) + except Exception as ex: + if not ignore_unknown: + raise ex + + def dns_records(self, domain): + data = self.request("infoDnsRecords", params={"domainname": domain}) + return [DNSRecord(**r) for r in data['responsedata']['dnsrecords']] + + def update_dns_zone(self, domain, zone: DNSZone): + if not isinstance(zone, DNSZone): + raise TypeError("Zone has to be instance of DNSZone") + + self.request("updateDnsZone", params={ + "domainname": domain, + "dnszone": zone.__dict__ + }) + + def dns_zone(self, domain): + data = self.request("infoDnsZone", params={"domainname": domain}) + return DNSZone(**data['responsedata']) + + def __init__(self, customer, api_key, api_password, timeout=5): + self.__customer = customer + self.__api_key = api_key + self.__api_password = api_password + self.__api_timeout = timeout + + def __enter__(self): + self.login() + return self + + def __exit__(self, exc_type, exc_value, traceback): + self.logout() diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..fb7a0e5 --- /dev/null +++ b/setup.py @@ -0,0 +1,27 @@ +#!/usr/bin/env python + +from setuptools import setup + +setup( + name='nc_dnsapi', + version='0.1.0', + description='API wrapper for the netcup DNS api', + author='Nicolai Buchwitz', + author_email='nb@tipi-net.de', + zip_safe=False, + include_package_data=True, + classifiers=[ + 'Development Status :: 4 - Beta', + 'Intended Audience :: Developers', + 'License :: OSI Approved :: BSD License', + 'Natural Language :: English', + 'Operating System :: POSIX :: Linux', + 'Programming Language :: Python', + ], + install_requires=[ + 'requests', + ], + packages=[ + 'nc_dnsapi', + ], + ) \ No newline at end of file