Skip to content
This repository was archived by the owner on Jul 21, 2023. It is now read-only.

Commit e0dc190

Browse files
committed
Initial commit.
0 parents  commit e0dc190

File tree

17 files changed

+2117
-0
lines changed

17 files changed

+2117
-0
lines changed

.gitignore

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
/credentials.yml
2+
/playground/
3+
/tests/output
4+
/tests/integration/integration_config.yml
5+
/felixfontein-hosttech_dns-*.tar.gz
6+
__pycache__

COPYING

Lines changed: 674 additions & 0 deletions
Large diffs are not rendered by default.

README.md

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Hosttech DNS Modules
2+
3+
This repository provides an [Ansible](https://github.com/ansible/ansible) collection with modules to create, modify and delete DNS records for zones hosted at the Swiss provider [Hosttech](https://www.hosttech.ch/) using their [API](https://ns1.hosttech.eu/public/api?wsdl).
4+
5+
## Tested with Ansible
6+
7+
Tested with both Ansible 2.9 and the current development version of Ansible.
8+
9+
## External requirements
10+
11+
No requirements.
12+
13+
## Included content
14+
15+
- ``hosttech_dns_record`` module
16+
- ``hosttech_dns_record_info`` module
17+
18+
## Using this collection
19+
20+
See [Ansible Using collections](https://docs.ansible.com/ansible/latest/user_guide/collections_using.html) for more details.
21+
22+
## Release notes
23+
24+
See [here](https://github.com/felixfontein/ansible-versioning_test_collection/tree/master/changelogs/CHANGELOG.rst).
25+
26+
## More information
27+
28+
- [Ansible Collection overview](https://github.com/ansible-collections/overview)
29+
- [Ansible User guide](https://docs.ansible.com/ansible/latest/user_guide/index.html)
30+
- [Ansible Developer guide](https://docs.ansible.com/ansible/latest/dev_guide/index.html)
31+
- [Ansible Community code of conduct](https://docs.ansible.com/ansible/latest/community/code_of_conduct.html)
32+
33+
## Licensing
34+
35+
GNU General Public License v3.0 or later.
36+
37+
See [COPYING](https://www.gnu.org/licenses/gpl-3.0.txt) to see the full text.

changelogs/config.yaml

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
changelog_filename_template: CHANGELOG.rst
2+
changelog_filename_version_depth: 0
3+
changes_file: changelog.yaml
4+
changes_format: combined
5+
keep_fragments: false
6+
mention_ancestor: true
7+
new_plugins_after_name: removed_features
8+
notesdir: fragments
9+
prelude_section_name: release_summary
10+
prelude_section_title: Release Summary
11+
sections:
12+
- - major_changes
13+
- Major Changes
14+
- - minor_changes
15+
- Minor Changes
16+
- - breaking_changes
17+
- Breaking Changes / Porting Guide
18+
- - deprecated_features
19+
- Deprecated Features
20+
- - removed_features
21+
- Removed Features (previously deprecated)
22+
- - security_fixes
23+
- Security Fixes
24+
- - bugfixes
25+
- Bugfixes
26+
- - known_issues
27+
- Known Issues
28+
title: Hosttech DNS Modules

changelogs/fragments/.empty

Whitespace-only changes.

galaxy.yml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
namespace: felixfontein
2+
name: hosttech_dns
3+
version: 1.0.0
4+
readme: README.md
5+
authors:
6+
- Felix Fontein (https://github.com/felixfontein)
7+
description: null
8+
license: GPL-3.0-or-later
9+
license_file: COPYING
10+
tags:
11+
- hosttech
12+
- dns
13+
dependencies: {}
14+
repository: https://github.com/felixfontein/ansible-hosttech_dns
15+
documentation: https://github.com/felixfontein/ansible-hosttech_dns/tree/master/docs/index.html
16+
homepage: https://github.com/felixfontein/ansible-hosttech_dns
17+
issues: https://github.com/felixfontein/ansible-hosttech_dns/issues
18+
build_ignore:
19+
- 'felixfontein-hosttech_dns-*.tar.gz'
20+
- .gitignore

plugins/doc_fragments/hosttech.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) 2017-2020 Felix Fontein
4+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
6+
from __future__ import (absolute_import, division, print_function)
7+
__metaclass__ = type
8+
9+
10+
class ModuleDocFragment(object):
11+
12+
# Standard files documentation fragment
13+
DOCUMENTATION = r'''
14+
requirements:
15+
- lxml
16+
17+
options:
18+
hosttech_username:
19+
description:
20+
- The username for the Hosttech API user.
21+
required: yes
22+
type: str
23+
hosttech_password:
24+
description:
25+
- The password for the Hosttech API user.
26+
required: yes
27+
type: str
28+
'''
Lines changed: 265 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,265 @@
1+
# -*- coding: utf-8 -*-
2+
#
3+
# Copyright (c) 2017-2020 Felix Fontein
4+
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
5+
6+
from __future__ import (absolute_import, division, print_function)
7+
__metaclass__ = type
8+
9+
10+
from ansible_collections.felixfontein.hosttech_dns.plugins.module_utils.wsdl import (
11+
WSDLError, Composer,
12+
)
13+
14+
15+
def format_ttl(ttl):
16+
sec = ttl % 60
17+
ttl //= 60
18+
min = ttl % 60
19+
ttl //= 60
20+
h = ttl
21+
result = []
22+
if h:
23+
result.append('{0}h'.format(h))
24+
if min:
25+
result.append('{0}m'.format(min))
26+
if sec:
27+
result.append('{0}s'.format(sec))
28+
return ' '.join(result)
29+
30+
31+
class DNSRecord(object):
32+
def __init__(self):
33+
self.id = None
34+
self.zone = None
35+
self.type = None
36+
self.prefix = None
37+
self.target = None
38+
self.ttl = 86400 # 24 * 60 * 60
39+
self.priority = None
40+
41+
@staticmethod
42+
def create_from_encoding(source, type=None):
43+
result = DNSRecord()
44+
result.id = source['id']
45+
result.zone = source['zone']
46+
result.type = source.get('type', type)
47+
result.prefix = source.get('prefix')
48+
result.target = source.get('target')
49+
result.ttl = int(source['ttl'])
50+
result.priority = source.get('priority')
51+
return result
52+
53+
def encode(self, include_ids=False):
54+
result = {
55+
'type': self.type,
56+
'prefix': self.prefix,
57+
'target': self.target,
58+
'ttl': self.ttl,
59+
'priority': self.priority,
60+
}
61+
if include_ids:
62+
result['id'] = self.id
63+
result['zone'] = self.zone
64+
return result
65+
66+
def __str__(self):
67+
data = []
68+
if self.id:
69+
data.append('id: {0}'.format(self.id))
70+
if self.zone:
71+
data.append('zone: {0}'.format(self.zone))
72+
data.append('type: {0}'.format(self.type))
73+
if self.prefix:
74+
data.append('prefix: "{0}"'.format(self.prefix))
75+
else:
76+
data.append('prefix: (none)')
77+
data.append('target: "{0}"'.format(self.target))
78+
data.append('ttl: {0}'.format(format_ttl(self.ttl)))
79+
if self.priority:
80+
data.append('priority: {0}'.format(self.priority))
81+
return 'DNSRecord(' + ', '.join(data) + ')'
82+
83+
def __repr__(self):
84+
return self.encode(include_ids=True)
85+
86+
87+
class DNSZone(object):
88+
def __init__(self, name):
89+
self.id = None
90+
self.name = name
91+
self.email = None
92+
self.ttl = 10800 # 3 * 60 * 60
93+
self.nameserver = None
94+
self.serial = None
95+
self.template = None
96+
self.records = []
97+
98+
@staticmethod
99+
def create_from_encoding(source):
100+
result = DNSZone(source['name'])
101+
result.id = source['id']
102+
result.email = source.get('email')
103+
result.ttl = int(source['ttl'])
104+
result.nameserver = source['nameserver']
105+
result.serial = source['serial']
106+
result.template = source.get('template')
107+
result.records = [DNSRecord.create_from_encoding(record) for record in source['records']]
108+
return result
109+
110+
def encode(self):
111+
return {
112+
'id': self.id,
113+
'name': self.name,
114+
'email': self.email,
115+
'ttl': self.ttl,
116+
'nameserver': self.nameserver,
117+
'serial': self.serial,
118+
'template': self.template,
119+
'ns3': self.ns3,
120+
'records': [record.encode(include_ids=True) for record in self.records],
121+
}
122+
123+
def __str__(self):
124+
data = []
125+
if self.id:
126+
data.append('id: {0}'.format(self.id))
127+
data.append('name: {0}'.format(self.name))
128+
if self.email:
129+
data.append('email: {0}'.format(self.email))
130+
data.append('ttl: {0}'.format(format_ttl(self.ttl)))
131+
if self.nameserver:
132+
data.append('nameserver: {0}'.format(self.nameserver))
133+
if self.serial:
134+
data.append('serial: {0}'.format(self.serial))
135+
if self.template:
136+
data.append('template: {0}'.format(self.template))
137+
for record in self.records:
138+
data.append('record: {0}'.format(str(record)))
139+
return 'DNSZone(\n' + ',\n'.join([' ' + line for line in data]) + '\n)'
140+
141+
def __repr__(self):
142+
return self.encode()
143+
144+
145+
class HostTechAPIError(Exception):
146+
pass
147+
148+
149+
class HostTechAPIAuthError(Exception):
150+
pass
151+
152+
153+
class HostTechAPI(object):
154+
def __init__(self, username, password, api='https://ns1.hosttech.eu/public/api', debug=False):
155+
"""
156+
Create a new HostTech API instance with given username and password.
157+
"""
158+
self._api = api
159+
self._username = username
160+
self._password = password
161+
self._debug = debug
162+
163+
def _prepare(self):
164+
command = Composer(self._api)
165+
command.add_auth(self._username, self._password)
166+
return command
167+
168+
def _announce(self, msg):
169+
pass
170+
# q.q('{0} {1} {2}'.format('=' * 4, msg, '=' * 40))
171+
172+
def _execute(self, command, result_name, acceptable_types):
173+
if self._debug:
174+
pass
175+
# q.q('Request: {0}'.format(command))
176+
try:
177+
result = command.execute(debug=self._debug)
178+
except WSDLError as e:
179+
if e.error_code == '998':
180+
raise HostTechAPIAuthError('Error on authentication ({0})'.format(e.error_message))
181+
raise
182+
res = result.get_result(result_name)
183+
if isinstance(res, acceptable_types):
184+
if self._debug:
185+
pass
186+
# q.q('Extracted result: {0} (type {1})'.format(res, type(res)))
187+
return res
188+
if self._debug:
189+
pass
190+
# q.q('Result: {0}; extracted type {1}'.format(result, type(res)))
191+
raise HostTechAPIError('Result has unexpected type {0} (expecting {1})!'.format(type(res), acceptable_types))
192+
193+
def get_zone(self, search):
194+
"""
195+
Search a zone by name or id.
196+
197+
@param search: The search string, i.e. a zone name or ID (string)
198+
@return The zone information (DNSZone)
199+
"""
200+
if self._debug:
201+
self._announce('get zone')
202+
command = self._prepare()
203+
command.add_simple_command('getZone', sZoneName=search)
204+
try:
205+
return DNSZone.create_from_encoding(self._execute(command, 'getZoneResponse', dict))
206+
except WSDLError as e:
207+
if e.error_origin == 'server' and e.error_message == 'zone not found':
208+
return None
209+
raise
210+
211+
def add_record(self, search, record):
212+
"""
213+
Adds a new record to an existing zone.
214+
215+
@param zone: The search string, i.e. a zone name or ID (string)
216+
@param record: The DNS record (DNSRecord)
217+
@return The created DNS record (DNSRecord)
218+
"""
219+
if self._debug:
220+
self._announce('add record')
221+
command = self._prepare()
222+
command.add_simple_command('addRecord', search=search, recorddata=record.encode(include_ids=False))
223+
try:
224+
return DNSRecord.create_from_encoding(self._execute(command, 'addRecordResponse', dict))
225+
except WSDLError as e:
226+
# FIXME
227+
raise
228+
229+
def update_record(self, record):
230+
"""
231+
Update a record.
232+
233+
@param record: The DNS record (DNSRecord)
234+
@return The DNS record (DNSRecord)
235+
"""
236+
if record.id is None:
237+
raise HostTechAPIError('Need record ID to update record!')
238+
if self._debug:
239+
self._announce('update record')
240+
command = self._prepare()
241+
command.add_simple_command('updateRecord', recordId=record.id, recorddata=record.encode(include_ids=False))
242+
try:
243+
return DNSRecord.create_from_encoding(self._execute(command, 'updateRecordResponse', dict))
244+
except WSDLError as e:
245+
# FIXME
246+
raise
247+
248+
def delete_record(self, record):
249+
"""
250+
Delete a record.
251+
252+
@param record: The DNS record (DNSRecord)
253+
@return True in case of success (boolean)
254+
"""
255+
if record.id is None:
256+
raise HostTechAPIError('Need record ID to delete record!')
257+
if self._debug:
258+
self._announce('delete record')
259+
command = self._prepare()
260+
command.add_simple_command('deleteRecord', recordId=record.id)
261+
try:
262+
return self._execute(command, 'deleteRecordResponse', bool)
263+
except WSDLError as e:
264+
# FIXME
265+
raise

0 commit comments

Comments
 (0)