diff --git a/README.md b/README.md index 724e49e..5c16f3a 100644 --- a/README.md +++ b/README.md @@ -27,7 +27,118 @@ collections: ## Using this collection -TODO +### Deploy happyDomain + +To setup happyDomain as a local service (support Docker, openrc and systemd), use the role `happydns.happydomain.happydomain`: + +```yaml + roles: + - name: happydns.happydomain.happydomain + use_container: no # yes if you want to use Docker instead +``` + +### Create a user account on your happyDomain instance + +```yaml + tasks: + - happydns.happydomain.user: + username: frederic@happydomain.org + password: "mySuperS3cur3P4$$w0rd" +``` + +This will create and enabled the user (no need to validate the email). + + +### Register a NS provider + +Eg. for an AXFR/DDNS provider: + +```yaml + tasks: + - happydns.happydomain.provider: + name: test + type: DDNSServer + data: + server: 192.168.0.42 + keyname: ddns + algorithm: hmac-sha256 + keyblob: yourBASE64Secret== + happydomain_username: frederic@happydomain.org + happydomain_password: "mySuperS3cur3P4$$w0rd" +``` + + +### Handle a new domain name in happyDomain + +```yaml + tasks: + - happydns.happydomain.domain: + provider: test + domain: happydomain.tf + happydomain_username: frederic@happydomain.org + happydomain_password: "mySuperS3cur3P4$$w0rd" +``` + +### Create a new record for a domain + +First, you need a zoneid: + +```yaml + tasks: + - happydns.happydomain.domain: + provider: test + domain: happydomain.tf + happydomain_username: frederic@happydomain.org + happydomain_password: "mySuperS3cur3P4$$w0rd" + register: my_zone +``` + +Note the `register`ed variable. + +Then, use the `happydns.happydomain.service` module: + +```yaml + tasks: + - happydns.happydomain.service: + happydomain_username: frederic@happydomain.org + happydomain_password: "mySuperS3cur3P4$$w0rd" + domain: happydomain.tf + zone: "{{ my_zone.current_zone }}" + subdomain: "test" + type: abstract.Server + service: + A: 127.0.0.1 + AAAA: "::1" + apply_changes: yes +``` + +This will add two records under `test.happydomain.tf`: A and AAAA (part of `abstract.Server`). + + +### Remove a given record + +You'll also need a zoneid, see previous section. Then: + +```yaml + tasks: + - happydns.happydomain.service: + happydomain_username: frederic@happydomain.org + happydomain_password: "mySuperS3cur3P4$$w0rd" + domain: happydomain.tf + zone: "{{ my_zone.current_zone }}" + subdomain: "test" + type: scvs.TXT + service: + content: "This is a test record" + state: absent + apply_changes: yes +``` + +This will remove all records matching: + +``` +test IN TXT "This is a test record" +``` ## Code of Conduct diff --git a/galaxy.yml b/galaxy.yml index e2fa07a..d1e3c0f 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: happydns name: happydomain -version: 0.2.3 +version: 0.3.0 readme: README.md authors: - happyDomain Team diff --git a/plugins/modules/__init__.py b/plugins/modules/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/plugins/modules/domain.py b/plugins/modules/domain.py new file mode 100644 index 0000000..085c8a8 --- /dev/null +++ b/plugins/modules/domain.py @@ -0,0 +1,91 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +''' + +from ansible.module_utils.basic import AnsibleModule + +from happydomain.api import HappyDomain + +def main(): + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['absent', 'present']), + happydomain_username=dict(type='str', aliases=['email']), + happydomain_password=dict(type='str', aliases=['passwd'], no_log=True), + happydomain_token=dict(type='str'), + happydomain_scheme=dict(type='str', default='http'), + happydomain_host=dict(type='str', default='localhost'), + happydomain_port=dict(type='int', default='8081'), + happydomain_baseurl=dict(type='str', default=''), + provider=dict(type='str'), + domain=dict(type='str', aliases=['name']), + import_zone=dict(type='bool', default=False), + ) + ) + + p = module.params + result = { + "changed": False + } + found = False + + a = HappyDomain( + scheme=p['happydomain_scheme'], + host=p['happydomain_host'], + port=p['happydomain_port'], + baseurl=p['happydomain_baseurl'], + token=p['happydomain_token'], + ) + + if p['happydomain_password'] is not None: + a.login(p['happydomain_username'], p['happydomain_password']) + + domains = a.domain_list() + + for d in domains: + if d.domain == p['domain'] or d.domain == p['domain'] + ".": + found = True + + if p['state'] == 'absent': + d.delete() + result['changed'] = True + result['msg'] = "domain " + p['domain'] + " deleted" + + elif len(d.zone_history) == 0 and p['import_zone']: + result['changed'] = True + result['current_zone'] = d.current_zone.id + result['msg'] += " and zone imported" + else: + result['current_zone'] = d.current_zone + break + + if not found: + providers = a.provider_list() + provider_found = False + for s in providers: + if s._comment == p['provider']: + provider_found = True + + dn = s.domain_add(p['domain']) + result['msg'] = "domain " + p['domain'] + " added" + + if p['import_zone']: + result['current_zone'] = dn.current_zone.id + result['msg'] += " and zone imported" + + if not provider_found: + module.fail_json(msg="No provider found with name " + p['provider']) + return + result['changed'] = True + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/provider.py b/plugins/modules/provider.py new file mode 100644 index 0000000..dd83526 --- /dev/null +++ b/plugins/modules/provider.py @@ -0,0 +1,79 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +''' + +from ansible.module_utils.basic import AnsibleModule + +from happydomain.api import HappyDomain + +def main(): + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['absent', 'present']), + happydomain_username=dict(type='str', aliases=['email']), + happydomain_password=dict(type='str', aliases=['passwd'], no_log=True), + happydomain_token=dict(type='str'), + happydomain_scheme=dict(type='str', default='http'), + happydomain_host=dict(type='str', default='localhost'), + happydomain_port=dict(type='int', default='8081'), + happydomain_baseurl=dict(type='str', default=''), + type=dict(type='str'), + name=dict(type='str', aliases=['comment']), + data=dict(type='dict'), + ) + ) + + p = module.params + changed = False + found = False + + a = HappyDomain( + scheme=p['happydomain_scheme'], + host=p['happydomain_host'], + port=p['happydomain_port'], + baseurl=p['happydomain_baseurl'], + token=p['happydomain_token'], + ) + + if p['happydomain_password'] is not None: + a.login(p['happydomain_username'], p['happydomain_password']) + + providers = a.provider_list() + + for s in providers: + if s._srctype == p['type'] and s._comment == p['name']: + found = True + + if p['state'] == 'absent': + s.delete() + changed = True + + else: + for k in p['data']: + if k not in s.args or p['data'][k] != s.args[k]: + s.args = p['data'] + changed = True + break + + if changed: + s.update() + break + + if not found and p['state'] != 'absent': + a.provider_add(p['type'], p['name'], p['data']) + changed = True + + module.exit_json( + changed=changed, + msg="provider " + p['name'] + ((" created" if not found else " altered") if p['state'] != 'absent' else " deleted"), + ) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/service.py b/plugins/modules/service.py new file mode 100644 index 0000000..2cc1e29 --- /dev/null +++ b/plugins/modules/service.py @@ -0,0 +1,109 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +''' + +from ansible.module_utils.basic import AnsibleModule + +from happydomain.api import HappyDomain + +def main(): + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['absent', 'present']), + happydomain_username=dict(type='str', aliases=['email']), + happydomain_password=dict(type='str', aliases=['passwd'], no_log=True), + happydomain_token=dict(type='str'), + happydomain_scheme=dict(type='str', default='http'), + happydomain_host=dict(type='str', default='localhost'), + happydomain_port=dict(type='int', default='8081'), + happydomain_baseurl=dict(type='str', default=''), + domain=dict(type='str', aliases=['name']), + zone=dict(type='str'), + type=dict(type='str'), + subdomain=dict(type='str'), + service=dict(type='dict'), + erase_others=dict(type='bool'), + apply_changes=dict(type='bool'), + ) + ) + + p = module.params + result = { + "changed": False + } + + a = HappyDomain( + scheme=p['happydomain_scheme'], + host=p['happydomain_host'], + port=p['happydomain_port'], + baseurl=p['happydomain_baseurl'], + token=p['happydomain_token'], + ) + + if p['happydomain_password'] is not None: + a.login(p['happydomain_username'], p['happydomain_password']) + + domains = a.domain_list() + + for d in domains: + if d.domain == p['domain'] or d.domain == p['domain'] + ".": + for z in d.zone_history: + if z == p['zone']: + zone = d.get_zone(z) + + if p['subdomain'].removesuffix(d.domain) in zone.services: + for s in zone.services[p['subdomain'].removesuffix(d.domain)]: + if s._svctype == p['type']: + differ = False + + for k in s.service: + if k not in p['service'] or s.service[k] != p['service'][k]: + differ = True + break + + if p['erase_others']: + if differ: + s.delete() + result['changed'] = True + elif not differ: + if p['state'] == 'absent': + s.delete() + result['changed'] = True + else: + module.exit_json(**result) + return + + if p['state'] != 'absent': + zone.add_zone_service( + p['subdomain'].removesuffix(d.domain), + p['type'], + p['service'], + ) + result['changed'] = True + + + if p['apply_changes']: + zone.apply_changes() + result['published'] = True + result['changed'] = True + + break + else: + module.fail_json(msg="No zone found with id " + p['zone']) + return + break + else: + module.fail_json(msg="No domain found with name " + p['domain']) + return + + module.exit_json(**result) + + +if __name__ == "__main__": + main() diff --git a/plugins/modules/user.py b/plugins/modules/user.py new file mode 100755 index 0000000..9a4b66d --- /dev/null +++ b/plugins/modules/user.py @@ -0,0 +1,73 @@ +# -*- coding: utf-8 -*- + +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +DOCUMENTATION = ''' +--- +''' + +from ansible.module_utils.basic import AnsibleModule + +from happydomain.admin import Admin + +def main(): + module = AnsibleModule( + argument_spec=dict( + state=dict(type='str', default='present', choices=['absent', 'present']), + username=dict(type='str', aliases=['name', 'email', 'happydomain_username']), + password=dict(type='str', aliases=['passwd', 'happydomain_password'], no_log=True), + allowcommercials=dict(type='bool'), + email_verified=dict(type='bool', default=True), + socket=dict(type='path', default='/var/lib/happydomain/happydomain.sock'), + ) + ) + + p = module.params + changed = False + found = False + + a = Admin(socket=p['socket']) + + users = a.authuser_list() + + for u in users: + if u.Email == p['username']: + found = True + + if p['state'] == 'absent': + u.Delete() + changed = True + + else: + changed = u.ResetPassword(p['password']) + + changedProp = False + if u.AllowCommercials != p['allowcommercials']: + u.AllowCommercials = p['allowcommercials'] + changedProp = True + + if u.EmailVerification is None and p['email_verified']: + now = datetime.now() + now = now.replace(microsecond=0) + u.EmailVerification = now.isoformat() + changedProp = True + + if changedProp: + u.Update() + changed = True + break + + if not found and p['state'] != 'absent': + a.authuser_create(p['username'], p['password'], p['allowcommercials'], p['email_verified']) + changed = True + + module.exit_json( + changed=changed, + msg="user " + p['username'] + ((" created" if not found else " altered") if p['state'] != 'absent' else " deleted"), + ) + + +if __name__ == "__main__": + main() diff --git a/roles/happydomain/tasks/download.yml b/roles/happydomain/tasks/download.yml index 377ae89..16d10f9 100644 --- a/roles/happydomain/tasks/download.yml +++ b/roles/happydomain/tasks/download.yml @@ -77,3 +77,9 @@ enabled: yes state: started when: ansible_service_mgr == "systemd" + +- name: Install happydomain python package + ansible.builtin.pip: + name: + - happydomain + - requests_unixsocket