Skip to content

Commit

Permalink
Merge pull request #1075 from rjeffman/automount_indirect_maps
Browse files Browse the repository at this point in the history
ipaautomountmap: add support for indirect maps
  • Loading branch information
t-woerner authored Jul 19, 2023
2 parents fef1bdc + a33fcf4 commit 5d08214
Show file tree
Hide file tree
Showing 4 changed files with 325 additions and 12 deletions.
25 changes: 19 additions & 6 deletions README-automountmap.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,21 @@ Example playbook to ensure presence of an automount map:
desc: "this is a map for servers in the DMZ"
```
Automount maps can contain a submount key, which defines a mount location within the map the references another map. On FreeIPA, this is known as an indirect map. An indirect automount map is equivalent to adding a proper automount key to a map, referencyng another map (this second map is the indirect map). Use `parent` and `mount` parameters to create an indirect automount map with ansible-freeipa, without the need to directly manage the automount keys.

Example playbook to ensure an indirect automount map is present:

```yaml
---
- name: Playbook to add an indirect automount map
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.indirect
location: DMZ
parent: auto.DMZ
mount: dmz_indirect
```

Example playbook to ensure auto.DMZi is absent:

```yaml
Expand Down Expand Up @@ -81,16 +96,14 @@ Variable | Description | Required
`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
`parentmap` | Parent map of the indirect map. Can only be used when creating new maps. Default: auto.master | no
`mount` | Indirect map mount point, relative to parent map. | yes, if `parent` is used.
`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
- Chris Procter
- Rafael Jeffman
14 changes: 14 additions & 0 deletions playbooks/automount/automount-map-indirect-map.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
---
- name: Managed automount maps
hosts: ipaserver
become: false
gather_facts: false

tasks:
- name: Playbook to add an indirect automount map
ipaautomountmap:
ipaadmin_password: SomeADMINpassword
name: auto.indirect
location: DMZ
parent: auto.DMZ
mount: dmz_indirect
155 changes: 149 additions & 6 deletions plugins/modules/ipaautomountmap.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
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
Expand All @@ -59,6 +60,16 @@
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
Expand All @@ -75,6 +86,14 @@
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
Expand Down Expand Up @@ -110,6 +129,35 @@ def get_automountmap(self, location, name):
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")
Expand All @@ -118,51 +166,142 @@ def check_ipa_params(self):
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.")
_args = {"automountmapname": mapname}
# 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):
name = self.params_get("name")
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(
Expand All @@ -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
Expand Down
Loading

0 comments on commit 5d08214

Please sign in to comment.