From 3ca9982c73646694a5fc1865487706f5ae0cb9af Mon Sep 17 00:00:00 2001
From: chrisp <chris@chrisprocter.co.uk>
Date: Tue, 12 Jan 2021 17:20:32 +0000
Subject: [PATCH] New automount key management module

There is a new automount key module placed in the plugins folder:

    plugins/modules/ipaautomountkey.py

The server module allows to ensure presence and absence of automount
keys. The module requires an existing automount location and map to
place the key within.

Here is the documentation for the module:

    README-automountkey.md

New example playbooks have been added:

    playbooks/automount/automount-key-absent.yaml
    playbooks/automount/automount-key-present.yaml

New tests for the module:

    tests/automount/test_automountkey.yml
---
 README-automountkey.md                        | 104 +++++++++
 .../automount/.automount-map-present.yml.swp  | Bin 0 -> 12288 bytes
 plugins/modules/ipaautomountkey.py            | 217 ++++++++++++++++++
 tests/automount/test_automountkey.yml         | 180 +++++++++++++++
 4 files changed, 501 insertions(+)
 create mode 100644 README-automountkey.md
 create mode 100644 playbooks/automount/.automount-map-present.yml.swp
 create mode 100644 plugins/modules/ipaautomountkey.py
 create mode 100644 tests/automount/test_automountkey.yml

diff --git a/README-automountkey.md b/README-automountkey.md
new file mode 100644
index 00000000..a4c1ab69
--- /dev/null
+++ b/README-automountkey.md
@@ -0,0 +1,104 @@
+Automountkey module
+=====================
+
+Description
+-----------
+
+The automountkey module allows the addition and removal 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 map:
+
+```yaml
+---
+- name: Playbook to add an automount map
+  hosts: ipaserver
+  become: true
+
+  tasks:
+  - name: create key TestKey
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      location: 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
+```
+
+Example playbook to rename an automount map:
+
+```yaml
+---
+- name: Playbook to add an automount map
+  - name: ensure key TestKey has been renamed to NewKeyName
+    ipaautomountkey:
+      ipaadmin_password: password01
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      newname: NewKeyName
+      state: rename
+```
+
+
+Variables
+=========
+
+ipaautomountkey
+-------
+
+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 
+`newkey` \| newname` \| `newautomountkey` | the name to change the key to if state is `rename` | yes when state is `rename`
+`info` \| `information` \| `automountinformation` | Mount information for the key | yes when state is `present`
+`state` | The state to ensure. It can be one of `present`, or `absent`, 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
GIT binary patch
literal 12288
zcmYc?2=nw+u+TGNU|?Vn01*&;jD?vYIio1ExB$Y(N0uiR7Z~Y5R2StZm!uZyCFkpv
zW~1w=gDBB2D9X=DO)e?cPs}UMOv*{sO)E-G%`8aNFUU!(Ov=yCF4j*hEy>T#FU>1K
zuyk`13v>&LQj1gbO7tpobI?s6<&TEIXb9jB0p3t!BSQm_8f7I#1!1935OWldhQMeD
zjE2By2#kinXb6mkz-S1JhQMeDjF1p0DPUx%XJBApg8G*aN;9I-Q0^!-8UmvsFd71*
zAut*OqaiRF0;3@?8UmvsFd71*Aut*OqaiQ^Lm)AQfuWs`f#Ei&<1fI#0HQ$N_`}b@
zaFL&ZVIMyO!+L%OhGqN=3@Q8!3|{;U4D$R848r^j46OVN44?QI81C{hFx-Ke0n$8*
zM?+vV1V%$(Gz3ONU^E0qLtr!nMnhmU1V%$(Gz11V1Qcip7#K)1ytpK>B-Kg*x}ckA
z&AJMic_pbud5Jj$wZL42!zL^y=_<JRMnTQZOUzBRg0eFU5))H$GxOpL5{rw=^NUif
z6oT_}QypD=J^f%pV5K01u$AjM`N@eTnfZAjLv$6u#wobw6_*yJD!^B_E5H;LgKSnv
z%u9iLPa&}=6)AK|5{t8oK}IE|Cg<m-S}Bwim8LQ%C}iXpmlRtmWELbArxul^7BRrA
Za)h}9ZU@XXh180~+=84`23=iU1^_ajy?p=x

literal 0
HcmV?d00001

diff --git a/plugins/modules/ipaautomountkey.py b/plugins/modules/ipaautomountkey.py
new file mode 100644
index 00000000..772ff2ed
--- /dev/null
+++ b/plugins/modules/ipaautomountkey.py
@@ -0,0 +1,217 @@
+#!/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/>.
+
+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=rename
+    required: True
+    choices: ["newname", "newautomountkey"]
+  info:
+    description: Mount information for the key
+    required: True
+    choices: ["information", "newinfo", "automountinformation"]
+  state:
+    description: State to ensure
+    required: False
+    default: present
+    choices: ["present", "absent", "rename"]
+'''
+
+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 (
+    FreeIPABaseModule, ipalib_errors
+)
+
+
+class AutomountKey(FreeIPABaseModule):
+
+    ipa_param_mapping = {
+        'automountkey': "key",
+        'automountmapautomountmapname': "mapname",
+    }
+
+    def get_key(self, location, mapname, keyname):
+        resp = dict()
+        try:
+            resp = self.api_command("automountkey_show",
+                                    location,
+                                    {"automountmapautomountmapname": mapname,
+                                     "automountkey": keyname})
+        except ipalib_errors.NotFound:
+            pass
+
+        return resp.get("result", None)
+
+    def check_ipa_params(self):
+        if not self.ipa_params.info and self.ipa_params.state == "present":
+            self.fail_json(msg="Value required for argument 'info'")
+
+        if self.ipa_params.state == "rename" and \
+           self.ipa_params.newname is None:
+            self.fail_json(msg="newname is required if state = 'rename'")
+
+    def define_ipa_commands(self):
+        args = self.get_ipa_command_args()
+        key = self.get_key(self.ipa_params.location,
+                           self.ipa_params.mapname,
+                           self.ipa_params.key)
+
+        if self.ipa_params.state == "present":
+            if key is None:
+                # does not exist and is wanted
+                args["automountinformation"] = self.ipa_params.info
+                self.add_ipa_command(
+                    "automountkey_add",
+                    name=self.ipa_params.location,
+                    args=args
+                )
+            elif key is not None:
+                # exists and is wanted, check for changes
+                if self.ipa_params.info != \
+                        key.get('automountinformation', [None])[0]:
+                    args["newautomountinformation"] = self.ipa_params.info
+                    self.add_ipa_command(
+                        "automountkey_mod",
+                        name=self.ipa_params.location,
+                        args=args
+                    )
+        elif self.ipa_params.state == "rename":
+            if key is not None:
+                newkey = self.get_key(self.ipa_params.location,
+                                      self.ipa_params.mapname,
+                                      self.ipa_params.newname)
+
+                args["rename"] = self.ipa_params.newname
+                if newkey is None:
+                    self.add_ipa_command(
+                        "automountkey_mod",
+                        name=self.ipa_params.location,
+                        args=args
+                    )
+        else:
+            # if key exists and self.ipa_params.state == "absent":
+            if key is not None:
+                self.add_ipa_command(
+                    "automountkey_del",
+                    name=self.ipa_params.location,
+                    args=args
+                )
+
+
+def main():
+    ipa_module = AutomountKey(
+        argument_spec=dict(
+            ipaadmin_principal=dict(type="str",
+                                    default="admin"
+                                    ),
+            ipaadmin_password=dict(type="str",
+                                   required=False,
+                                   no_log=True
+                                   ),
+            state=dict(type='str',
+                       default='present',
+                       choices=['present', 'absent', 'rename']
+                       ),
+            location=dict(type="str",
+                          aliases=["automountlocationcn", "automountlocation"],
+                          default=None,
+                          required=True
+                          ),
+            newname=dict(type="str",
+                         aliases=["newkey", "new_name",
+                                  "new_key", "newautomountkey"],
+                         default=None,
+                         required=False
+                         ),
+            mapname=dict(type="str",
+                         aliases=["map", "automountmapname", "automountmap"],
+                         default=None,
+                         required=True
+                         ),
+            key=dict(type="str",
+                     required=True,
+                     aliases=["name", "automountkey"]
+                     ),
+            info=dict(type="str",
+                      aliases=["information", "newinfo",
+                               "automountinformation"]
+                      ),
+        ),
+    )
+    ipa_module.ipa_run()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/tests/automount/test_automountkey.yml b/tests/automount/test_automountkey.yml
new file mode 100644
index 00000000..19c8922a
--- /dev/null
+++ b/tests/automount/test_automountkey.yml
@@ -0,0 +1,180 @@
+---
+- name: Test automountmap
+  hosts: ipaserver
+  become: true
+  gather_facts: false
+
+  tasks:
+  - 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 key TestKey is absent
+#    ipaautomountkey:
+#      ipaadmin_password: SomeADMINpassword
+#      automountlocationcn: TestLocation
+#      automountmapname: TestMap
+#      automountkey: TestKey
+#      state: absent
+
+  - name: create location TestLocation
+    ipaautomountlocation:
+      ipaadmin_password: SomeADMINpassword
+      name: TestLocation
+      state: present
+
+  - name: create map TestMap 
+    ipaautomountmap:
+      ipaadmin_password: SomeADMINpassword
+      name: TestMap
+      location: TestLocation
+      desc: "this is a test map that should be deleted by the test"
+
+### test the key creation, and modification
+  - name: ensure key TestKey is present
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      automountinformation: 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
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      automountinformation: 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
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      automountinformation: 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
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      automountinformation: 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
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      automountinformation: 192.168.122.1:/nfsshare
+      newname: NewKeyName
+      state: rename
+    register: result
+    failed_when: result.failed or not result.changed
+
+  - name: ensure key TestKey does not exist
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: NewKeyName
+      automountinformation: 192.168.122.1:/nfsshare
+      state: present
+    register: result
+    failed_when: result.failed or result.changed
+
+  - name: ensure key NewKeyName does exist
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: NewKeyName
+      automountinformation: 192.168.122.1:/nfsshare
+      state: present
+    register: result
+    failed_when: result.failed or result.changed
+
+  - name: ensure key TestKey has been renamed to NewKeyName again
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      automountinformation: 192.168.122.1:/nfsshare
+      newname: NewKeyName
+      state: rename
+    register: result
+    failed_when: result.failed or result.changed
+
+  - name: ensure failure when state=present and newname is not set
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: TestKey
+      automountinformation: 192.168.122.1:/nfsshare
+      state: rename
+    register: result
+    failed_when: not result.failed
+
+
+### cleanup after the tests
+  - name: ensure key NewKeyName is absent
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: NewKeyName
+      state: absent
+    register: result
+    failed_when: result.failed or not result.changed
+
+  - name: ensure key TestKey is absent again
+    ipaautomountkey:
+      ipaadmin_password: SomeADMINpassword
+      automountlocationcn: TestLocation
+      automountmapname: TestMap
+      automountkey: NewKeyName
+      state: absent
+    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
+
+  - name: ensure location TestLocation is removed
+    ipaautomountlocation:
+      ipaadmin_password: SomeADMINpassword
+      name: TestLocation
+      state: absent
+
-- 
GitLab