diff --git a/README-automountmap.md b/README-automountmap.md new file mode 100644 index 0000000000000000000000000000000000000000..208dd53deb5e6b7ddba8a42d85204577889c7ce9 --- /dev/null +++ b/README-automountmap.md @@ -0,0 +1,96 @@ +Automountmap module +===================== + +Description +----------- + +The automountmap module allows the addition and removal of maps within automount locations. + +It is desgined to follow the IPA api as closely as possible while ensuring ease of use. + + +Features +-------- +* Automount map management + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipaautomountmap 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 ensure presence of an automount map: + +```yaml +--- +- name: Playbook to add an automount map + hosts: ipaserver + become: no + + tasks: + - name: ensure map named auto.DMZ in location DMZ is created + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: auto.DMZ + location: DMZ + desc: "this is a map for servers in the DMZ" +``` + +Example playbook to ensure auto.DMZi is absent: + +```yaml +--- +- name: Playbook to remove an automount map + hosts: ipaserver + become: no + + tasks: + - name: ensure map auto.DMZ has been removed + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: auto.DMZ + location: DMZ + state: absent +``` + + +Variables +========= + +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` \| `mapname` \| `map` \| `automountmapname` | Name of the map to manage | yes +`location` \| `automountlocation` \| `automountlocationcn` | Location name. | yes +`desc` \| `description` | Description of the map | yes +`state` | The state to ensure. It can be one of `present`, or `absent`, default: `present`. | no + + +Notes +===== + +Creation of indirect mount points are not supported. + +Authors +======= + +Chris Procter diff --git a/playbooks/automount/automount-map-absent.yaml b/playbooks/automount/automount-map-absent.yaml new file mode 100644 index 0000000000000000000000000000000000000000..344fb20556907b4cd2015b82c391bf7ab63c819e --- /dev/null +++ b/playbooks/automount/automount-map-absent.yaml @@ -0,0 +1,12 @@ +--- +- name: Automount map absent example + hosts: ipaserver + become: no + + tasks: + - name: ensure map TestMap is absent + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + state: absent diff --git a/playbooks/automount/automount-map-present.yaml b/playbooks/automount/automount-map-present.yaml new file mode 100644 index 0000000000000000000000000000000000000000..88f4bb7b2fcb5550c762d1ca41a5c9ba7cc76c54 --- /dev/null +++ b/playbooks/automount/automount-map-present.yaml @@ -0,0 +1,12 @@ +--- +- name: Automount map present example + hosts: ipaserver + become: no + + tasks: + - name: ensure map TestMap is present + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + desc: "this is a test map" diff --git a/plugins/modules/ipaautomountmap.py b/plugins/modules/ipaautomountmap.py new file mode 100644 index 0000000000000000000000000000000000000000..72084396668be2b9ebe381c4973029846944e520 --- /dev/null +++ b/plugins/modules/ipaautomountmap.py @@ -0,0 +1,195 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# Authors: +# Chris Procter <cprocter@redhat.com> +# +# Copyright (C) 2021 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/>. + +from __future__ import (absolute_import, division, print_function) + +__metaclass__ = type + +ANSIBLE_METADATA = { + "metadata_version": "1.0", + "supported_by": "community", + "status": ["preview"], +} + + +DOCUMENTATION = ''' +--- +module: ipaautomountmap +author: Chris Procter +short_description: Manage FreeIPA autommount map +description: +- Add, delete, and modify an IPA automount map +options: + ipaadmin_principal: + description: The admin principal. + default: admin + ipaadmin_password: + description: The admin password. + required: false + automountlocation: + description: automount location map is anchored to + choices: ["location", "automountlocationcn"] + required: True + name: + description: automount map to be managed. + choices: ["mapname", "map", "automountmapname"] + required: True + desc: + description: description of automount map. + choices: ["description"] + required: false + state: + description: State to ensure + required: false + default: present + choices: ["present", "absent"] +''' + +EXAMPLES = ''' + - name: ensure map named auto.DMZ in location DMZ is present + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: auto.DMZ + location: DMZ + desc: "this is a map for servers in the DMZ" + + - name: remove a map named auto.DMZ in location DMZ if it exists + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: auto.DMZ + location: DMZ + state: absent +''' + +RETURN = ''' +''' + +from ansible.module_utils.ansible_freeipa_module import ( + IPAAnsibleModule, compare_args_ipa +) + + +class AutomountMap(IPAAnsibleModule): + + def __init__(self, *args, **kwargs): + # pylint: disable=super-with-arguments + super(AutomountMap, self).__init__(*args, **kwargs) + self.commands = [] + + def get_automountmap(self, location, name): + try: + response = self.ipa_command( + "automountmap_show", + location, + {"automountmapname": name, "all": True} + ) + except Exception: # pylint: disable=broad-except + return None + else: + return response["result"] + + def check_ipa_params(self): + invalid = [] + name = self.params_get("name") + state = self.params_get("state") + if state == "present": + if len(name) != 1: + self.fail_json(msg="Exactly one name must be provided \ + for state=present.") + if state == "absent": + if len(name) == 0 : + self.fail_json(msg="Argument 'map_type' can not be used with " + "state 'absent'") + invalid = ["desc"] + + self.params_fail_used_invalid(invalid, state) + + def get_args(self, mapname, desc): # pylint: disable=no-self-use + _args = {} + if mapname: + _args["automountmapname"] = mapname + if desc: + _args["description"] = desc + return _args + + def define_ipa_commands(self): + name = self.params_get("name") + state = self.params_get("state") + location = self.params_get("location") + desc = self.params_get("desc") + + for mapname in name: + automountmap = self.get_automountmap(location, mapname) + + if state == "present": + args = self.get_args(mapname, desc) + if automountmap is None: + self.commands.append([location, "automountmap_add", args]) + else: + if not compare_args_ipa(self, args, automountmap): + self.commands.append( + [location, "automountmap_mod", args] + ) + + if state == "absent": + if automountmap is not None: + self.commands.append([ + location, + "automountmap_del", + {"automountmapname": [mapname]} + ]) + + +def main(): + ipa_module = AutomountMap( + argument_spec=dict( + state=dict(type='str', + default='present', + choices=['present', 'absent'] + ), + location=dict(type="str", + aliases=["automountlocation", "automountlocationcn"], + default=None, + required=True + ), + name=dict(type="list", + aliases=["mapname", "map", "automountmapname"], + default=None, + required=True + ), + desc=dict(type="str", + aliases=["description"], + required=False, + default=None + ), + ), + ) + changed = False + ipaapi_context = ipa_module.params_get("ipaapi_context") + with ipa_module.ipa_connect(context=ipaapi_context): + ipa_module.check_ipa_params() + ipa_module.define_ipa_commands() + changed = ipa_module.execute_ipa_commands(ipa_module.commands) + ipa_module.exit_json(changed=changed) + + +if __name__ == "__main__": + main() diff --git a/tests/automount/test_automountmap.yml b/tests/automount/test_automountmap.yml new file mode 100644 index 0000000000000000000000000000000000000000..cbf5db406f9f8b468e7702b85be20df4f0c6c143 --- /dev/null +++ b/tests/automount/test_automountmap.yml @@ -0,0 +1,147 @@ +--- +- name: Test automountmap + hosts: ipaserver + become: no + gather_facts: no + + tasks: + # setup environment + - name: ensure test maps are absent + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: + - TestMap01 + - TestMap02 + location: TestLocation + state: absent + + - name: ensure location TestLocation is absent + ipaautomountlocation: + ipaadmin_password: SomeADMINpassword + name: TestLocation + state: absent + + - name: ensure map TestMap is absent + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + state: absent + + - name: ensure location TestLocation is present + ipaautomountlocation: + ipaadmin_password: SomeADMINpassword + name: TestLocation + state: present + + # TESTS + - block: + - name: ensure map TestMap is present + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + desc: "this is a test map that should be deleted by the test" + register: result + failed_when: result.failed or not result.changed + + - name: ensure map TestMap is present again + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + register: result + failed_when: result.failed or result.changed + + - name: ensure map TestMap has a different description + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + desc: "this is a changed description that should be deleted by the test" + register: result + failed_when: result.failed or not result.changed + + - name: ensure map TestMap has a different description, again + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + desc: "this is a changed description that should be deleted by the test" + register: result + failed_when: result.failed or result.changed + + - name: ensure map TestMap is removed + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + state: absent + register: result + failed_when: result.failed or not result.changed + + - name: ensure map TestMap has been removed + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + state: absent + register: result + failed_when: result.failed or result.changed + + - name: ensure map TestMap01 is present + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap01 + location: TestLocation + desc: "this is a changed description that should be deleted by the test" + register: result + failed_when: result.failed or not result.changed + + - name: ensure map TestMap02 is present + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap02 + location: TestLocation + desc: "this is a changed description that should be deleted by the test" + register: result + failed_when: result.failed or not result.changed + + - name: ensure TestMap01 and TestMap02 are both absent + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: + - TestMap01 + - TestMap02 + location: TestLocation + state: absent + register: result + failed_when: result.failed or not result.changed + + - name: ensure TestMap01 and TestMap02 are both absent again + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: + - TestMap01 + - TestMap02 + location: TestLocation + state: absent + register: result + failed_when: result.failed or result.changed + + # CLEAN UP + always: + - name: ensure test maps are absent + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: + - TestMap01 + - TestMap02 + location: TestLocation + state: absent + + - name: ensure location TestLocation is absent + ipaautomountlocation: + ipaadmin_password: SomeADMINpassword + name: TestLocation + state: absent