mirror of
https://github.com/freeipa/ansible-freeipa.git
synced 2026-05-07 13:53:23 +00:00
Merge pull request #671 from rjeffman/baseclass_playground
Deprecate FreeIPABaseModule in favor of IPAAnsibleModule.
This commit is contained in:
@@ -1,6 +1,5 @@
|
||||
# Writing a new Ansible FreeIPA module
|
||||
|
||||
## Minimum requirements
|
||||
A ansible-freeipa module should have:
|
||||
|
||||
* Code:
|
||||
@@ -13,68 +12,4 @@ A ansible-freeipa module should have:
|
||||
* Tests:
|
||||
* Test cases (also playbooks) defined in `tests/<module_name>/test_<something>.yml`. It's ok to have multiple files in this directory.
|
||||
|
||||
## Code
|
||||
|
||||
The module file have to start with the python shebang line, license header and definition of the constants `ANSIBLE_METADATA`, `DOCUMENTATION`, `EXAMPLES` and `RETURNS`. Those constants need to be defined before the code (even imports). See https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html#starting-a-new-module for more information.
|
||||
|
||||
|
||||
Although it's use is not yet required, ansible-freeipa provides `FreeIPABaseModule` as a helper class for the implementation of new modules. See the example bellow:
|
||||
|
||||
```python
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import FreeIPABaseModule
|
||||
|
||||
|
||||
class SomeIPAModule(FreeIPABaseModule):
|
||||
ipa_param_mapping = {
|
||||
"arg_to_be_passed_to_ipa_command": "module_param",
|
||||
"another_arg": "get_another_module_param",
|
||||
}
|
||||
|
||||
def get_another_module_param(self):
|
||||
another_module_param = self.ipa_params.another_module_param
|
||||
|
||||
# Validate or modify another_module_param ...
|
||||
|
||||
return another_module_param
|
||||
|
||||
def check_ipa_params(self):
|
||||
|
||||
# Validate your params here ...
|
||||
|
||||
# Example:
|
||||
if not self.ipa_params.module_param in VALID_OPTIONS:
|
||||
self.fail_json(msg="Invalid value for argument module_param")
|
||||
|
||||
def define_ipa_commands(self):
|
||||
args = self.get_ipa_command_args()
|
||||
|
||||
self.add_ipa_command("some_ipa_command", name="obj-name", args=args)
|
||||
|
||||
|
||||
def main():
|
||||
ipa_module = SomeIPAModule(argument_spec=dict(
|
||||
module_param=dict(type="str", default=None, required=False),
|
||||
another_module_param=dict(type="str", default=None, required=False),
|
||||
))
|
||||
ipa_module.ipa_run()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
```
|
||||
|
||||
In the example above, the module will call the command `some_ipa_command`, using "obj-name" as name and, `arg_to_be_passed_to_ipa_command` and `another_arg` as arguments.
|
||||
|
||||
The values of the arguments will be determined by the class attribute `ipa_param_mapping`.
|
||||
|
||||
In the case of `arg_to_be_passed_to_ipa_command` the key (`module_param`) is defined in the module `argument_specs` so the value of the argument is actually used.
|
||||
|
||||
On the other hand, `another_arg` as mapped to something else: a callable method. In this case the method will be called and it's result used as value for `another_arg`.
|
||||
|
||||
**NOTE**: Keep mind that to take advantage of the parameters mapping defined in `ipa_param_mapping` you will have to call `args = self.get_ipa_command_args()` and use `args` in your command. There is no implicit call of this method.
|
||||
|
||||
|
||||
## Disclaimer
|
||||
|
||||
The `FreeIPABaseModule` is new and might not be suitable to all cases and every module yet. In case you need to extend it's functionality for a new module please open an issue or PR and we'll be happy to discuss it.
|
||||
Use the script `utils/new_module` to create the stub files for a new module.
|
||||
|
||||
@@ -68,13 +68,16 @@ RETURN = '''
|
||||
'''
|
||||
|
||||
from ansible.module_utils.ansible_freeipa_module import (
|
||||
FreeIPABaseModule, ipalib_errors
|
||||
IPAAnsibleModule, ipalib_errors
|
||||
)
|
||||
|
||||
|
||||
class AutomountLocation(FreeIPABaseModule):
|
||||
class AutomountLocation(IPAAnsibleModule):
|
||||
|
||||
ipa_param_mapping = {}
|
||||
def __init__(self, *args, **kwargs):
|
||||
# pylint: disable=super-with-arguments
|
||||
super(AutomountLocation, self).__init__(*args, **kwargs)
|
||||
self.commands = []
|
||||
|
||||
def get_location(self, location):
|
||||
try:
|
||||
@@ -87,40 +90,28 @@ class AutomountLocation(FreeIPABaseModule):
|
||||
return response.get("result", None)
|
||||
|
||||
def check_ipa_params(self):
|
||||
if len(self.ipa_params.name) == 0:
|
||||
if len(self.params_get("name")) == 0:
|
||||
self.fail_json(msg="At least one location must be provided.")
|
||||
|
||||
def define_ipa_commands(self):
|
||||
state = self.params_get("state")
|
||||
|
||||
for location_name in self.ipa_params.name:
|
||||
for location_name in self.params_get("name"):
|
||||
location = self.get_location(location_name)
|
||||
|
||||
if not location and self.ipa_params.state == "present":
|
||||
if not location and state == "present":
|
||||
# does not exist and is wanted
|
||||
self.add_ipa_command(
|
||||
"automountlocation_add",
|
||||
name=location_name,
|
||||
args=None,
|
||||
)
|
||||
elif location and self.ipa_params.state == "absent":
|
||||
self.commands.append(
|
||||
(location_name, "automountlocation_add", {}))
|
||||
elif location and state == "absent":
|
||||
# exists and is not wanted
|
||||
self.add_ipa_command(
|
||||
"automountlocation_del",
|
||||
name=location_name,
|
||||
args=None,
|
||||
)
|
||||
self.commands.append(
|
||||
(location_name, "automountlocation_del", {}))
|
||||
|
||||
|
||||
def main():
|
||||
ipa_module = AutomountLocation(
|
||||
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']
|
||||
@@ -132,7 +123,12 @@ def main():
|
||||
),
|
||||
),
|
||||
)
|
||||
ipa_module.ipa_run()
|
||||
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__":
|
||||
|
||||
@@ -203,11 +203,13 @@ dnszone:
|
||||
|
||||
from ipapython.dnsutil import DNSName # noqa: E402
|
||||
from ansible.module_utils.ansible_freeipa_module import (
|
||||
FreeIPABaseModule,
|
||||
IPAAnsibleModule,
|
||||
is_ip_address,
|
||||
is_ip_network_address,
|
||||
is_valid_port,
|
||||
ipalib_errors
|
||||
ipalib_errors,
|
||||
compare_args_ipa,
|
||||
IPAParamMapping,
|
||||
) # noqa: E402
|
||||
import netaddr
|
||||
from ansible.module_utils import six
|
||||
@@ -217,31 +219,39 @@ if six.PY3:
|
||||
unicode = str
|
||||
|
||||
|
||||
class DNSZoneModule(FreeIPABaseModule):
|
||||
class DNSZoneModule(IPAAnsibleModule):
|
||||
|
||||
ipa_param_mapping = {
|
||||
# Direct Mapping
|
||||
"idnsforwardpolicy": "forward_policy",
|
||||
"idnssoarefresh": "refresh",
|
||||
"idnssoaretry": "retry",
|
||||
"idnssoaexpire": "expire",
|
||||
"idnssoaminimum": "minimum",
|
||||
"dnsttl": "ttl",
|
||||
"dnsdefaultttl": "default_ttl",
|
||||
"idnsallowsyncptr": "allow_sync_ptr",
|
||||
"idnsallowdynupdate": "dynamic_update",
|
||||
"idnssecinlinesigning": "dnssec",
|
||||
"idnsupdatepolicy": "update_policy",
|
||||
# Mapping by method
|
||||
"idnsforwarders": "get_ipa_idnsforwarders",
|
||||
"idnsallowtransfer": "get_ipa_idnsallowtransfer",
|
||||
"idnsallowquery": "get_ipa_idnsallowquery",
|
||||
"idnssoamname": "get_ipa_idnssoamname",
|
||||
"idnssoarname": "get_ipa_idnssoarname",
|
||||
"skip_nameserver_check": "get_ipa_skip_nameserver_check",
|
||||
"skip_overlap_check": "get_ipa_skip_overlap_check",
|
||||
"nsec3paramrecord": "get_ipa_nsec3paramrecord",
|
||||
}
|
||||
def __init__(self, *args, **kwargs):
|
||||
# pylint: disable=super-with-arguments
|
||||
super(DNSZoneModule, self).__init__(*args, **kwargs)
|
||||
|
||||
ipa_param_mapping = {
|
||||
# Direct Mapping
|
||||
"idnsforwardpolicy": "forward_policy",
|
||||
"idnssoarefresh": "refresh",
|
||||
"idnssoaretry": "retry",
|
||||
"idnssoaexpire": "expire",
|
||||
"idnssoaminimum": "minimum",
|
||||
"dnsttl": "ttl",
|
||||
"dnsdefaultttl": "default_ttl",
|
||||
"idnsallowsyncptr": "allow_sync_ptr",
|
||||
"idnsallowdynupdate": "dynamic_update",
|
||||
"idnssecinlinesigning": "dnssec",
|
||||
"idnsupdatepolicy": "update_policy",
|
||||
# Mapping by method
|
||||
"idnsforwarders": self.get_ipa_idnsforwarders,
|
||||
"idnsallowtransfer": self.get_ipa_idnsallowtransfer,
|
||||
"idnsallowquery": self.get_ipa_idnsallowquery,
|
||||
"idnssoamname": self.get_ipa_idnssoamname,
|
||||
"idnssoarname": self.get_ipa_idnssoarname,
|
||||
"skip_nameserver_check": self.get_ipa_skip_nameserver_check,
|
||||
"skip_overlap_check": self.get_ipa_skip_overlap_check,
|
||||
"nsec3paramrecord": self.get_ipa_nsec3paramrecord,
|
||||
}
|
||||
|
||||
self.commands = []
|
||||
self.ipa_params = IPAParamMapping(self, ipa_param_mapping)
|
||||
self.exit_args = {}
|
||||
|
||||
def validate_ips(self, ips, error_msg):
|
||||
invalid_ips = [
|
||||
@@ -441,39 +451,34 @@ class DNSZoneModule(FreeIPABaseModule):
|
||||
for zone_name in self.get_zone_names():
|
||||
# Look for existing zone in IPA
|
||||
zone, is_zone_active = self.get_zone(zone_name)
|
||||
args = self.get_ipa_command_args(zone=zone)
|
||||
args = self.ipa_params.get_ipa_command_args(zone=zone)
|
||||
|
||||
if self.ipa_params.state in ["present", "enabled", "disabled"]:
|
||||
if not zone:
|
||||
# Since the zone doesn't exist we just create it
|
||||
# with given args
|
||||
self.add_ipa_command("dnszone_add", zone_name, args)
|
||||
self.commands.append((zone_name, "dnszone_add", args))
|
||||
is_zone_active = True
|
||||
# just_added = True
|
||||
|
||||
else:
|
||||
# Zone already exist so we need to verify if given args
|
||||
# matches the current config. If not we updated it.
|
||||
if self.require_ipa_attrs_change(args, zone):
|
||||
self.add_ipa_command("dnszone_mod", zone_name, args)
|
||||
if not compare_args_ipa(self, args, zone):
|
||||
self.commands.append((zone_name, "dnszone_mod", args))
|
||||
|
||||
if self.ipa_params.state == "enabled" and not is_zone_active:
|
||||
self.add_ipa_command("dnszone_enable", zone_name)
|
||||
self.commands.append((zone_name, "dnszone_enable", {}))
|
||||
|
||||
if self.ipa_params.state == "disabled" and is_zone_active:
|
||||
self.add_ipa_command("dnszone_disable", zone_name)
|
||||
self.commands.append((zone_name, "dnszone_disable", {}))
|
||||
|
||||
if self.ipa_params.state == "absent" and zone is not None:
|
||||
self.add_ipa_command("dnszone_del", zone_name)
|
||||
self.commands.append((zone_name, "dnszone_del", {}))
|
||||
|
||||
def process_command_result(self, name, command, args, result):
|
||||
# pylint: disable=super-with-arguments
|
||||
super(DNSZoneModule, self).process_command_result(
|
||||
name, command, args, result
|
||||
)
|
||||
def process_results(self, _result, command, name, _args, exit_args):
|
||||
if command == "dnszone_add" and self.ipa_params.name_from_ip:
|
||||
dnszone_exit_args = self.exit_args.setdefault('dnszone', {})
|
||||
dnszone_exit_args['name'] = name
|
||||
exit_args.setdefault('dnszone', {})["name"] = name
|
||||
|
||||
|
||||
def get_argument_spec():
|
||||
@@ -532,12 +537,24 @@ def get_argument_spec():
|
||||
|
||||
|
||||
def main():
|
||||
DNSZoneModule(
|
||||
ansible_module = DNSZoneModule(
|
||||
argument_spec=get_argument_spec(),
|
||||
mutually_exclusive=[["name", "name_from_ip"]],
|
||||
required_one_of=[["name", "name_from_ip"]],
|
||||
supports_check_mode=True,
|
||||
).ipa_run()
|
||||
)
|
||||
|
||||
exit_args = {}
|
||||
ipaapi_context = ansible_module.params_get("ipaapi_context")
|
||||
with ansible_module.ipa_connect(context=ipaapi_context):
|
||||
ansible_module.check_ipa_params()
|
||||
ansible_module.define_ipa_commands()
|
||||
changed = ansible_module.execute_ipa_commands(
|
||||
ansible_module.commands,
|
||||
result_handler=DNSZoneModule.process_results,
|
||||
exit_args=exit_args
|
||||
)
|
||||
ansible_module.exit_json(changed=changed, **exit_args)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
Reference in New Issue
Block a user