diff --git a/README-location.md b/README-location.md new file mode 100644 index 0000000000000000000000000000000000000000..81a7033de8991018a9c59966f8ca4a94f05e0bfe --- /dev/null +++ b/README-location.md @@ -0,0 +1,90 @@ +Location module +=============== + +Description +----------- + +The location module allows to ensure presence and absence of locations. + +Features +-------- + +* Location management + + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipalocation module. + + +Requirements +------------ + +**Controller** +* Ansible version: 2.8+ + +**Node** +* Supported FreeIPA version (see above) + + +Usage +===== + +Example inventory file + +```ini +[ipaserver] +ipaserver.test.local +``` + + +Example playbook to make sure location "my_location1" is present: + +```yaml +--- +- name: Playbook to manage IPA location. + hosts: ipaserver + become: yes + + tasks: + - ipalocation: + name: my_location1 + description: My Location 1 +``` + + +Example playbook to make sure location "my_location1" is absent: + +```yaml +--- +- name: Playbook to manage IPA location. + hosts: ipaserver + become: yes + + tasks: + - ipalocation: + name: my_location1 + state: absent +``` + + +Variables +--------- + +ipalocation +------- + +Variable | Description | Required +-------- | ----------- | -------- +`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no +`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no +`name` \| `idnsname` | The list of location name strings. | yes +`description` | The IPA location string | false +`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no + + +Authors +======= + +Thomas Woerner diff --git a/playbooks/location/location-absent.yml b/playbooks/location/location-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..aa99fe6ffebe5ca948b8dac036452884a5ac6146 --- /dev/null +++ b/playbooks/location/location-absent.yml @@ -0,0 +1,10 @@ +--- +- name: Location absent test + hosts: ipaserver + become: true + + tasks: + - name: Ensure location my_location1 is absent + ipalocation: + name: my_location1 + state: absent diff --git a/playbooks/location/location-present.yml b/playbooks/location/location-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..76d44a52c39e8837a7b080e863630aaafbe894b9 --- /dev/null +++ b/playbooks/location/location-present.yml @@ -0,0 +1,9 @@ +--- +- name: Location present test + hosts: ipaserver + become: true + + tasks: + - name: Ensure location my_location1 is present + ipalocation: + name: my_location1 diff --git a/plugins/modules/ipalocation.py b/plugins/modules/ipalocation.py new file mode 100644 index 0000000000000000000000000000000000000000..a36596cff6d2ce9a3c48425701902554e31ff9fa --- /dev/null +++ b/plugins/modules/ipalocation.py @@ -0,0 +1,218 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Authors: +# Thomas Woerner <twoerner@redhat.com> +# +# Copyright (C) 2020 Red Hat +# see file 'COPYING' for use and warranty information +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see <http://www.gnu.org/licenses/>. + +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "supported_by": "community", + "status": ["preview"], +} + +DOCUMENTATION = """ +--- +module: ipalocation +short description: Manage FreeIPA location +description: Manage FreeIPA location +options: + ipaadmin_principal: + description: The admin principal. + default: admin + ipaadmin_password: + description: The admin password. + required: false + name: + description: The list of location name strings. + required: true + aliases: ["idnsname"] + description: + description: The IPA location string + required: false + state: + description: The state to ensure. + choices: ["present", "absent"] + default: present + required: true +""" + +EXAMPLES = """ +# Ensure location my_location1 is present +- ipalocation: + name: my_location1 + description: My location 1 + +# Ensure location my_location1 is absent +- ipalocation: + name: my_location1 + state: absent +""" + +RETURN = """ +""" + + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.ansible_freeipa_module import \ + temp_kinit, temp_kdestroy, valid_creds, api_connect, api_command, \ + compare_args_ipa, module_params_get +import six + +if six.PY3: + unicode = str + + +def find_location(module, name): + """Find if a location with the given name already exist.""" + try: + _result = api_command(module, "location_show", name, {"all": True}) + except Exception: # pylint: disable=broad-except + # An exception is raised if location name is not found. + return None + else: + return _result["result"] + + +def gen_args(description): + _args = {} + if description is not None: + _args["description"] = description + return _args + + +def main(): + ansible_module = AnsibleModule( + argument_spec=dict( + # general + ipaadmin_principal=dict(type="str", default="admin"), + ipaadmin_password=dict(type="str", required=False, no_log=True), + + name=dict(type="list", aliases=["idnsname"], + default=None, required=True), + # present + description=dict(required=False, type='str', default=None), + # state + state=dict(type="str", default="present", + choices=["present", "absent"]), + ), + supports_check_mode=True, + ) + + ansible_module._ansible_debug = True + + # Get parameters + + # general + ipaadmin_principal = module_params_get(ansible_module, + "ipaadmin_principal") + ipaadmin_password = module_params_get(ansible_module, "ipaadmin_password") + names = module_params_get(ansible_module, "name") + + # present + description = module_params_get(ansible_module, "description") + + # state + state = module_params_get(ansible_module, "state") + + # Check parameters + + if state == "present": + if len(names) != 1: + ansible_module.fail_json( + msg="Only one location be added at a time.") + + if state == "absent": + if len(names) < 1: + ansible_module.fail_json(msg="No name given.") + invalid = ["description"] + for x in invalid: + if vars()[x] is not None: + ansible_module.fail_json( + msg="Argument '%s' can not be used with state '%s'" % + (x, state)) + + # Init + + changed = False + exit_args = {} + ccache_dir = None + ccache_name = None + try: + if not valid_creds(ansible_module, ipaadmin_principal): + ccache_dir, ccache_name = temp_kinit(ipaadmin_principal, + ipaadmin_password) + api_connect() + + commands = [] + for name in names: + # Make sure location exists + res_find = find_location(ansible_module, name) + + # Create command + if state == "present": + + # Generate args + args = gen_args(description) + + # Found the location + if res_find is not None: + # For all settings is args, check if there are + # different settings in the find result. + # If yes: modify + if not compare_args_ipa(ansible_module, args, + res_find): + commands.append([name, "location_mod", args]) + else: + commands.append([name, "location_add", args]) + + elif state == "absent": + if res_find is not None: + commands.append([name, "location_del", {}]) + + else: + ansible_module.fail_json(msg="Unkown state '%s'" % state) + + # Execute commands + + for name, command, args in commands: + try: + result = api_command(ansible_module, command, name, + args) + if "completed" in result: + if result["completed"] > 0: + changed = True + else: + changed = True + except Exception as e: + ansible_module.fail_json(msg="%s: %s: %s" % (command, name, + str(e))) + + except Exception as e: + ansible_module.fail_json(msg=str(e)) + + finally: + temp_kdestroy(ccache_dir, ccache_name) + + # Done + + ansible_module.exit_json(changed=changed, **exit_args) + + +if __name__ == "__main__": + main() diff --git a/tests/location/test_location.yml b/tests/location/test_location.yml new file mode 100644 index 0000000000000000000000000000000000000000..c267d06316504a34cf1f907658e70e108994198f --- /dev/null +++ b/tests/location/test_location.yml @@ -0,0 +1,64 @@ +--- +- name: Test location + hosts: ipaserver + become: true + + tasks: + + # CLEANUP TEST ITEMS + + - name: Ensure location my_location1 is absent + ipalocation: + name: my_location1 + state: absent + + # CREATE TEST ITEMS + + # TESTS + + - name: Ensure location my_location1 is present + ipalocation: + name: my_location1 + register: result + failed_when: not result.changed or result.failed + + - name: Ensure location my_location1 is present again + ipalocation: + name: my_location1 + register: result + failed_when: result.changed or result.failed + + - name: Ensure location my_location1 is present with description + ipalocation: + name: my_location1 + description: My Location 1 + register: result + failed_when: not result.changed or result.failed + + - name: Ensure location my_location1 is present again with description + ipalocation: + name: my_location1 + description: My Location 1 + register: result + failed_when: result.changed or result.failed + + - name: Ensure location my_location1 is absent + ipalocation: + name: my_location1 + state: absent + register: result + failed_when: not result.changed or result.failed + + - name: Ensure location my_location1 is absent again + ipalocation: + name: my_location1 + state: absent + register: result + failed_when: result.changed or result.failed + + # CLEANUP TEST ITEMS + + - name: Ensure location my_location1 is absent + ipalocation: + name: my_location1 + state: absent