ipaautomountmap: add support for indirect maps

Indirect maps were not supported by ansible-freeipa ipaautomountmap.
This patch adds support for adding indirect automount maps using the
"parent" and "mount" parameters, if the map do not yet exist. An
existing map cannot be modified.

The "parent" parameter must match an existing automount map, and the
"mount" parameter is required if "parent" is used.

A new example playbook can be found at:

    playbooks/automount/automount-map-indirect-map.yml

A new test playbook was added to test the feature:

    tests/automount/test_automountmap_indirect.yml
This commit is contained in:
Rafael Guterres Jeffman
2023-04-10 20:36:26 -03:00
parent fef1bdcf8e
commit a33fcf45f8
4 changed files with 325 additions and 12 deletions

View File

@@ -37,6 +37,7 @@ module: ipaautomountmap
author:
- Chris Procter (@chr15p)
- Thomas Woerner (@t-woerner)
- Rafael Jeffman (@rjeffman)
short_description: Manage FreeIPA autommount map
description:
- Add, delete, and modify an IPA automount map
@@ -59,6 +60,16 @@ options:
type: str
aliases: ["description"]
required: false
parentmap:
description: |
Parent map of the indirect map. Can only be used when creating
new maps.
type: str
required: false
mount:
description: Indirect map mount point, relative to parent map.
type: str
required: false
state:
description: State to ensure
type: str
@@ -75,6 +86,14 @@ EXAMPLES = '''
location: DMZ
desc: "this is a map for servers in the DMZ"
- name: ensure indirect map exists
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.INDIRECT
location: DMZ
parentmap: auto.DMZ
mount: indirect
- name: remove a map named auto.DMZ in location DMZ if it exists
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
@@ -110,6 +129,35 @@ class AutomountMap(IPAAnsibleModule):
else:
return response["result"]
def get_indirect_map_keys(self, location, name):
"""Check if 'name' is an indirect map for 'parentmap'."""
try:
maps = self.ipa_command("automountmap_find", location, {})
except Exception: # pylint: disable=broad-except
return []
result = []
for check_map in maps.get("result", []):
_mapname = check_map['automountmapname'][0]
keys = self.ipa_command(
"automountkey_find",
location,
{
"automountmapautomountmapname": _mapname,
"all": True
}
)
cmp_value = (
name if _mapname == "auto.master" else "ldap:{0}".format(name)
)
result.extend([
(location, _mapname, key.get("automountkey")[0])
for key in keys.get("result", [])
for mount_info in key.get("automountinformation", [])
if cmp_value in mount_info
])
return result
def check_ipa_params(self):
invalid = []
name = self.params_get("name")
@@ -118,15 +166,27 @@ class AutomountMap(IPAAnsibleModule):
if len(name) != 1:
self.fail_json(msg="Exactly one name must be provided for"
" 'state: present'.")
mount = self.params_get("mount") or False
parentmap = self.params_get("parentmap")
if parentmap:
if not mount:
self.fail_json(
msg="Must provide 'mount' parameter for indirect map."
)
elif parentmap != "auto.master" and mount[0] == "/":
self.fail_json(
msg="mount point is relative to parent map, "
"cannot begin with '/'"
)
if state == "absent":
if len(name) == 0:
self.fail_json(msg="At least one 'name' must be provided for"
" 'state: absent'")
invalid = ["desc"]
invalid = ["desc", "parentmap", "mount"]
self.params_fail_used_invalid(invalid, state)
def get_args(self, mapname, desc):
def get_args(self, mapname, desc, parentmap, mount):
# automountmapname is required for all automountmap operations.
if not mapname:
self.fail_json(msg="automountmapname cannot be None or empty.")
@@ -134,6 +194,11 @@ class AutomountMap(IPAAnsibleModule):
# An empty string is valid and will clear the attribute.
if desc is not None:
_args["description"] = desc
# indirect map attributes
if parentmap is not None:
_args["parentmap"] = parentmap
if mount is not None:
_args["key"] = mount
return _args
def define_ipa_commands(self):
@@ -141,28 +206,102 @@ class AutomountMap(IPAAnsibleModule):
state = self.params_get("state")
location = self.params_get("location")
desc = self.params_get("desc")
mount = self.params_get("mount")
parentmap = self.params_get("parentmap")
for mapname in name:
automountmap = self.get_automountmap(location, mapname)
is_indirect_map = any([parentmap, mount])
if state == "present":
args = self.get_args(mapname, desc)
args = self.get_args(mapname, desc, parentmap, mount)
if automountmap is None:
self.commands.append([location, "automountmap_add", args])
if is_indirect_map:
if (
parentmap and
self.get_automountmap(location, parentmap) is None
):
self.fail_json(msg="Parent map does not exist.")
self.commands.append(
[location, "automountmap_add_indirect", args]
)
else:
self.commands.append(
[location, "automountmap_add", args]
)
else:
if not compare_args_ipa(self, args, automountmap):
has_changes = not compare_args_ipa(
self, args, automountmap, ['parentmap', 'key']
)
if is_indirect_map:
map_config = (
location, parentmap or "auto.master", mount
)
indirects = self.get_indirect_map_keys(
location, mapname
)
if map_config not in indirects or has_changes:
self.fail_json(
msg="Indirect maps can only be created, "
"not modified."
)
elif has_changes:
self.commands.append(
[location, "automountmap_mod", args]
)
if state == "absent":
elif state == "absent":
def find_keys(parent_loc, parent_map, parent_key):
return self.ipa_command(
"automountkey_show",
parent_loc,
{
"automountmapautomountmapname": parent_map,
"automountkey": parent_key,
}
).get("result")
if automountmap is not None:
indirects = self.get_indirect_map_keys(location, mapname)
# Remove indirect map configurations for this map
self.commands.extend([
(
ploc,
"automountkey_del",
{
"automountmapautomountmapname": pmap,
"automountkey": pkey,
}
)
for ploc, pmap, pkey in indirects
if find_keys(ploc, pmap, pkey)
])
# Remove map
self.commands.append([
location,
"automountmap_del",
{"automountmapname": [mapname]}
])
# ensure commands are unique and automountkey commands are
# executed first in the list
def hashable_dict(dictionaire):
return tuple(
(k, tuple(v) if isinstance(v, (list, tuple)) else v)
for k, v in dictionaire.items()
)
cmds = [
(name, cmd, hashable_dict(args))
for name, cmd, args in self.commands
]
self.commands = [
(name, cmd, dict(args))
for name, cmd, args in
sorted(set(cmds), key=lambda cmd: cmd[1])
]
def main():
ipa_module = AutomountMap(
@@ -184,6 +323,10 @@ def main():
required=False,
default=None
),
parentmap=dict(
type="str", required=False, default=None
),
mount=dict(type="str", required=False, default=None),
),
)
changed = False