diff --git a/README-automountkey.md b/README-automountkey.md new file mode 100644 index 0000000000000000000000000000000000000000..cb49b5b59dbe1b611c4cc0cacf7e1f94de2e65b4 --- /dev/null +++ b/README-automountkey.md @@ -0,0 +1,112 @@ +Automountkey module +===================== + +Description +----------- + +The automountkey module allows management of keys within an automount map. + +It is desgined to follow the IPA api as closely as possible while ensuring ease of use. + + +Features +-------- +* Automount key management + +Supported FreeIPA Versions +-------------------------- + +FreeIPA versions 4.4.0 and up are supported by the ipaautomountkey 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 key: + +```yaml +--- +- name: Playbook to manage automount key + hosts: ipaserver + + tasks: + - name: ensure automount key TestKey is present + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + mapname: TestMap + key: TestKey + info: 192.168.122.1:/exports + state: present +``` + +Example playbook to rename an automount map: + +```yaml +--- +- name: Playbook to add an automount map + hosts: ipaserver + + tasks: + - name: ensure aumount key TestKey is renamed to NewKeyName + ipaautomountkey: + ipaadmin_password: password01 + automountlocationcn: TestLocation + automountmapname: TestMap + automountkey: TestKey + newname: NewKeyName + state: renamed +``` + +Example playbook to ensure an automount key is absent: + +```yaml +--- +- name: Playbook to manage an automount key + hosts: ipaserver + + tasks: + - name: ensure automount key TestKey is absent + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + mapname: TestMap + key: TestKey + 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 +`location` \| `automountlocationcn` \| `automountlocation` | Location name. | yes +`mapname` \| `map` \| `automountmapname` \| `automountmap` | Map the key belongs to | yes +`key` \| `name` \| `automountkey` | Automount key to manage | yes +`rename` \| `new_name` \| `newautomountkey` | the name to change the key to if state is `renamed` | yes when state is `renamed` +`info` \| `information` \| `automountinformation` | Mount information for the key | yes when state is `present` +`state` | The state to ensure. It can be one of `present`, `absent` or `renamed`, default: `present`. | no + +Authors +======= + +Chris Procter diff --git a/playbooks/automount/.automount-map-present.yml.swp b/playbooks/automount/.automount-map-present.yml.swp new file mode 100644 index 0000000000000000000000000000000000000000..1a9bbc91a11e1d02cb72378a07472de6debf2cc1 Binary files /dev/null and b/playbooks/automount/.automount-map-present.yml.swp differ diff --git a/playbooks/automount/automountkey-present.yml b/playbooks/automount/automountkey-present.yml new file mode 100644 index 0000000000000000000000000000000000000000..50f51bc359f73ac50b5d1226f253641ad652c8d2 --- /dev/null +++ b/playbooks/automount/automountkey-present.yml @@ -0,0 +1,13 @@ +--- +- name: Playbook to manage an automout key + hosts: ipaserver + + tasks: + - name: Ensure autmount key is present + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + mapname: TestMap + key: TestKey + info: 192.168.122.1:/exports + state: present diff --git a/playbooks/automount/automountkey-renamed.yml b/playbooks/automount/automountkey-renamed.yml new file mode 100644 index 0000000000000000000000000000000000000000..ff60d1170334d995c5992121c85dc2d9ccbbdfe1 --- /dev/null +++ b/playbooks/automount/automountkey-renamed.yml @@ -0,0 +1,13 @@ +--- +- name: Playbook to manage an automount key + hosts: ipaserver + + tasks: + - name: Ensure aumount key TestKey is renamed to NewKeyName + ipaautomountkey: + ipaadmin_password: password01 + automountlocationcn: TestLocation + automountmapname: TestMap + automountkey: TestKey + newname: NewKeyName + state: renamed diff --git a/playbooks/automount/automoutkey-absent.yml b/playbooks/automount/automoutkey-absent.yml new file mode 100644 index 0000000000000000000000000000000000000000..99eadbe3881873581a00ab1fb62c27eb8fd33881 --- /dev/null +++ b/playbooks/automount/automoutkey-absent.yml @@ -0,0 +1,12 @@ +--- +- name: Playbook to manage an automount key + hosts: ipaserver + + tasks: + - name: Ensure autmount key is present + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + mapname: TestMap + key: TestKey + state: absent diff --git a/plugins/modules/ipaautomountkey.py b/plugins/modules/ipaautomountkey.py new file mode 100644 index 0000000000000000000000000000000000000000..8eac689635d63b5ac017f2094118b84e7ef18312 --- /dev/null +++ b/plugins/modules/ipaautomountkey.py @@ -0,0 +1,235 @@ +#!/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: ipaautomountkey +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 + location: + description: automount location map is in + required: True + choices: ["automountlocationcn", "automountlocation"] + mapname: + description: automount map to be managed + choices: ["map", "automountmapname", "automountmap"] + required: True + key: + description: automount key to be managed + required: True + choices: ["name", "automountkey"] + newkey: + description: key to change to if state is 'renamed' + required: True + choices: ["newname", "newautomountkey"] + info: + description: Mount information for the key + required: True + choices: ["information", "automountinformation"] + state: + description: State to ensure + required: False + default: present + choices: ["present", "absent", "renamed"] +''' + +EXAMPLES = ''' + - name: create key TestKey + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + locationcn: TestLocation + mapname: TestMap + key: TestKey + info: 192.168.122.1:/exports + state: present + + - name: ensure key TestKey is absent + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + mapname: TestMap + key: TestKey + state: absent +''' + +RETURN = ''' +''' + +from ansible.module_utils.ansible_freeipa_module import ( + IPAAnsibleModule, ipalib_errors +) + + +class AutomountKey(IPAAnsibleModule): + + def __init__(self, *args, **kwargs): + # pylint: disable=super-with-arguments + super(AutomountKey, self).__init__(*args, **kwargs) + self.commands = [] + + def get_key(self, location, mapname, key): + try: + args = { + "automountmapautomountmapname": mapname, + "automountkey": key, + "all": True, + } + resp = self.ipa_command("automountkey_show", location, args) + except ipalib_errors.NotFound: + return None + else: + return resp.get("result") + + def check_ipa_params(self): + invalid = [] + state = self.params_get("state") + if state == "present": + invalid = ["rename"] + if not self.params_get("info"): + self.fail_json(msg="Value required for argument 'info'") + + if state == "rename": + invalid = ["info"] + if not self.params_get("rename"): + self.fail_json(msg="Value required for argument 'renamed'") + + if state == "absent": + invalid = ["info", "rename"] + + self.params_fail_used_invalid(invalid, state) + + @staticmethod + def get_args(mapname, key, info, rename): + _args = {} + if mapname: + _args["automountmapautomountmapname"] = mapname + if key: + _args["automountkey"] = key + if info: + _args["automountinformation"] = info + if rename: + _args["rename"] = rename + return _args + + def define_ipa_commands(self): + state = self.params_get("state") + location = self.params_get("location") + mapname = self.params_get("mapname") + key = self.params_get("key") + info = self.params_get("info") + rename = self.params_get("rename") + + args = self.get_args(mapname, key, info, rename) + + res_find = self.get_key(location, mapname, key) + + if state == "present": + if res_find is None: + # does not exist and is wanted + self.commands.append([location, "automountkey_add", args]) + else: + # exists and is wanted, check for changes + if info not in res_find.get("automountinformation"): + self.commands.append([location, "automountkey_mod", args]) + + if state == "renamed": + if res_find is None: + self.fail_json( + msg=( + "Cannot rename inexistent key: '%s', '%s', '%s'" + % (location, mapname, key) + ) + ) + self.commands.append([location, "automountkey_mod", args]) + + if state == "absent": + # if key exists and self.ipa_params.state == "absent": + if res_find is not None: + self.commands.append([location, "automountkey_del", args]) + + +def main(): + ipa_module = AutomountKey( + argument_spec=dict( + state=dict( + type='str', + choices=['present', 'absent', 'renamed'], + required=None, + default='present', + ), + location=dict( + type="str", + aliases=["automountlocationcn", "automountlocation"], + required=True, + ), + rename=dict( + type="str", + aliases=["new_name", "newautomountkey"], + required=False, + ), + mapname=dict( + type="str", + aliases=["map", "automountmapname", "automountmap"], + required=True, + ), + key=dict( + type="str", + aliases=["name", "automountkey"], + required=True, + ), + info=dict( + type="str", + aliases=["information", "automountinformation"], + required=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_automountkey.yml b/tests/automount/test_automountkey.yml new file mode 100644 index 0000000000000000000000000000000000000000..6ab6bebab83b0002eaa8a2f74ace4efecb3bc7a3 --- /dev/null +++ b/tests/automount/test_automountkey.yml @@ -0,0 +1,154 @@ +--- +- name: Test automountmap + hosts: "{{ ipa_test_host | default('ipaserver') }}" + become: no + gather_facts: no + + tasks: + - name: ensure test location TestLocation is present + ipaautomountlocation: + ipaadmin_password: SomeADMINpassword + name: TestLocation + + - name: ensure test map TestMap is present + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + + - name: ensure key NewKeyName is absent + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: NewKeyName + state: absent + + - name: ensure key TestKey is absent + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: NewKeyName + state: absent + + - block: + ### test the key creation, and modification + - name: ensure key TestKey is present + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: TestKey + info: 192.168.122.1:/exports + state: present + register: result + failed_when: result.failed or not result.changed + + - name: ensure key TestKey is present again + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: TestKey + info: 192.168.122.1:/exports + state: present + register: result + failed_when: result.failed or result.changed + + ## modify the key + - name: ensure key TestKey information has been updated + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: TestKey + info: 192.168.122.1:/nfsshare + state: present + register: result + failed_when: result.failed or not result.changed + + - name: ensure key TestKey information has been updated again + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: TestKey + info: 192.168.122.1:/nfsshare + state: present + register: result + failed_when: result.failed or result.changed + + ## modify the name + - name: ensure key TestKey has been renamed to NewKeyName + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: TestKey + new_name: NewKeyName + state: renamed + register: result + failed_when: result.failed or not result.changed + + - name: ensure key TestKey is absent + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: TestKey + state: absent + register: result + failed_when: result.failed or result.changed + + - name: ensure key NewKeyName is present + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: NewKeyName + info: 192.168.122.1:/nfsshare + state: present + register: result + failed_when: result.failed or result.changed + + - name: ensure failure when state is renamed and newname is not set + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: TestKey + state: renamed + register: result + failed_when: not result.failed + + ### cleanup after the tests + always: + - name: ensure key NewKeyName is absent + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: NewKeyName + state: absent + + - name: ensure key TestKey is absent + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + location: TestLocation + map: TestMap + key: NewKeyName + state: absent + + - name: ensure map TestMap is absent + ipaautomountmap: + ipaadmin_password: SomeADMINpassword + name: TestMap + location: TestLocation + state: absent + + - name: ensure location TestLocation is absent + ipaautomountlocation: + ipaadmin_password: SomeADMINpassword + name: TestLocation + state: absent diff --git a/tests/automount/test_automountkey_client_context.yml b/tests/automount/test_automountkey_client_context.yml new file mode 100644 index 0000000000000000000000000000000000000000..e6d611b249d7731d688b9426fbc43858b761e4fb --- /dev/null +++ b/tests/automount/test_automountkey_client_context.yml @@ -0,0 +1,41 @@ +--- +- name: Test automountkey + hosts: ipaclients, ipaserver + become: no + gather_facts: no + + tasks: + - name: Include FreeIPA facts. + include_tasks: ../env_freeipa_facts.yml + + # Test will only be executed if host is not a server. + - name: Execute with server context in the client. + ipaautomountkey: + ipaadmin_password: SomeADMINpassword + ipaapi_context: server + location: NoLocation + map: NoMap + key: ThisShouldNotWork + register: result + failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*")) + when: ipa_host_is_client + +# Import basic module tests, and execute with ipa_context set to 'client'. +# If ipaclients is set, it will be executed using the client, if not, +# ipaserver will be used. +# +# With this setup, tests can be executed against an IPA client, against +# an IPA server using "client" context, and ensure that tests are executed +# in upstream CI. + +- name: Test automountlocation using client context, in client host. + import_playbook: test_automountkey.yml + when: groups['ipaclients'] + vars: + ipa_test_host: ipaclients + +- name: Test automountlocation using client context, in server host. + import_playbook: test_automountkey.yml + when: groups['ipaclients'] is not defined or not groups['ipaclients'] + vars: + ipa_context: client