Compare commits

...

30 Commits

Author SHA1 Message Date
Thomas Woerner
a1f882ac52 Merge pull request #694 from rjeffman/ipaservice_code_optimization
ipaservice: code refactor
2021-12-23 14:10:18 +01:00
Rafael Guterres Jeffman
8d99ce4207 Merge pull request #711 from t-woerner/automember_fixes
Automember fixes
2021-12-23 10:03:18 -03:00
Rafael Guterres Jeffman
e3e7d71474 Fix automember test
Automember client test was failing as it was using 'name' along with 'state: rebuilt'.
2021-12-22 14:09:28 -03:00
Thomas Woerner
285fb6b8e0 automember: Add support for action: orphans_removed
The removal of group or hostgroup orphans has been added to the automember
module.

It can be ensured that orphans have been removed using action: orphans_removed
The automember_type needs to be set for this.

New examples have been added to README-automember.md

New playbooks:
- playbooks/automember/automember-group-orphans-removed.yml
- playbooks/automember/automember-hostgroup-orphans-removed.yml

New tests:
- tests/automember/test_automember_orphans_removed.yml
2021-12-22 15:27:13 +01:00
Thomas Woerner
d2648b142a automember: Add automember default group handling
The fallback group and hostgroup for unmached entries can be set and
unset using default_group. If default_group is empty, then the default
group will be unset.

DN and ipa_get_based provided by ansible_freeipa_module are used in the
code.

New parameters:
- default_group: Default (fallback) group for all unmatched entries.

New parameters and examples have been added to README-automember.md

New playbooks:
- playbooks/automember/automember-default-group-not-set.yml
- playbooks/automember/automember-default-group-set.yml
- playbooks/automember/automember-default-hostgroup-not-set.yml
- playbooks/automember/automember-default-hostgroup-set.yml

New tests:
- tests/automember/test_automember_default_group.yml
2021-12-22 15:26:57 +01:00
Thomas Woerner
67e192242c automember: Add automember state: rebuilt
There was state: rebuild before, but the code was incomplete and was not
able to run properly.

New parameters:
- users: Limit the rebuild to the given users only
- hosts: Limit the rebuild to the given hosts only
- no_wait: Don't wait for rebuilding membership

New parameters and examples have been added to README-automember.md

tests/automember/test_automember_client_context.yml has been using
state: rebuild and lacked the automember_type parameter.

grouping was used in functions and has been replaced by automember_type.

Some typos in examples have been fixed also.

New playbooks:
- playbooks/automember/automember-group-membership-all-users-rebuilt.yml
- playbooks/automember/automember-group-membership-users-rebuilt.yml
- playbooks/automember/automember-hostgroup-membership-all-hosts-rebuilt.yml
- playbooks/automember/automember-hostgroup-membership-hosts-rebuilt.yml

New tests:
- tests/automember/test_automember_rebuilt.yml
2021-12-22 12:50:08 +01:00
Thomas Woerner
9eefc1ae7c ansible_freeipa_module: New api_get_basedn, IPAAnsibleModule.ipa_get_basedn
These functions have been added to get the basedb from api.env for use
with DN for example.

api_get_basedn is returning api.env.basedn
IPAAnsibleModule.ipa_get_basedn is a wrapper for api_get_basedn
2021-12-22 12:17:10 +01:00
Rafael Guterres Jeffman
b9d0b35e83 Merge pull request #709 from t-woerner/automationhub_fixes2
More Automation Hub fixes
2021-12-14 17:59:46 -03:00
Thomas Woerner
85006d611f Fix ansible-test reported pep8 errors
These are indent issues, one item per line for argument_specs items
containing options dicts and missing or overflow spaces for comments
and dict delimiters.
2021-12-14 18:37:37 +01:00
Thomas Woerner
82412ef761 ipabackup_get_backup_dir.py: Add missing ":" in example 2021-12-14 18:24:37 +01:00
Thomas Woerner
2e178e5a38 Ignore file for ansible-test sanity 2.12
These are skips for python 2.6 with import-2.6!skip and
compile-2.6!skip, ignores for the exit_raw_json function in
ansible_freeipa_module.py for use with ipavault, the ignore of using
automatic field numbering for the string format function, the use of
string split without maxsplits (not working in Python2), skips of the
shebang tests for scripts in tests and utils and the ignore of missing
collection_name for deprecate function calls in
ansible_freeipa_module.py.
2021-12-14 18:23:55 +01:00
Thomas Woerner
f47d134335 utils/gen_module_docs.py: Drop duplicate setup_adtrust key 2021-12-14 17:53:25 +01:00
Thomas Woerner
541c514aa9 Add version for ansible deprecated calls 2021-12-14 17:52:58 +01:00
Thomas Woerner
85257b9d03 build-galaxy-release: Real cleanup of ipabackup_get_backup_dir.py link
plugins/modules/ipabackup_* needs to be cleaned up not
plugins/action/ipabackup_*
2021-12-14 17:26:19 +01:00
Thomas Woerner
e7f902ca48 Merge pull request #671 from rjeffman/baseclass_playground
Deprecate FreeIPABaseModule in favor of IPAAnsibleModule.
2021-12-13 14:03:14 +01:00
Thomas Woerner
a1bfa608e2 Merge pull request #705 from rjeffman/fix_yamllint_issues
yamllint: Fix missing document start.
2021-12-10 16:30:48 +01:00
Rafael Guterres Jeffman
eefd94d13f yamllint: Fix missing document start.
Recently added Ubuntu configuration filesi for roles miss document
start marker.
2021-12-09 22:16:26 -03:00
Thomas Woerner
667177db07 Merge pull request #699 from rjeffman/ubuntu_fix_roles
Fix role issues in Debian based distros.
2021-12-09 15:55:40 +01:00
Thomas Woerner
f301ad55aa Merge pull request #703 from jh23453/patch-2
correct comment in example playbook
2021-12-09 15:48:18 +01:00
Thomas Woerner
fd9ecc246d Merge pull request #702 from jh23453/patch-1
Login shell is called defaultshell and not defaultlogin
2021-12-09 14:24:27 +01:00
Thomas Woerner
b15552540c Merge pull request #698 from rjeffman/ci_centos9_stream_images
upstream ci: Build images for CentOS 9 Stream.
2021-12-09 12:58:17 +01:00
jh23453
25b41b7eca correct comment in example playbook
Insert "ensure maxlife is set to 49 in global policy" instead of the wrong comment from cut&paste.
2021-12-08 17:27:08 +01:00
jh23453
b53f2a08d6 Login shell is called defaultshell and not defaultlogin
The example didn't work for me with the following error (on freeipa 4.9.8):

TASK [display default login shell] **************************************************************************************************
fatal: [freeipa1.example.org]: FAILED! => {"msg": "
The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'defaultlogin'

Using the correct variable defaultshell works for me.
2021-12-08 17:20:10 +01:00
Rafael Guterres Jeffman
f2b3e88d5a ipaservice: code refactor.
This PR refactors ipaservice to reduce the number of variables (in
favor of a 'struct') and to group member management code so that it
can be leveraged, and not partially duplicated, between the states
and actions.

Altough this code is less direct that the previous one, it will reduce
the number fo changes to be made if changes to member management is
required.
2021-11-30 14:35:16 -03:00
Rafael Guterres Jeffman
e001ecb1de upstream ci: Build images for CentOS 9 Stream.
This patch add support for building testing images for CentOS 9 Stream.
2021-11-30 13:43:49 -03:00
Rafael Guterres Jeffman
44b3c3003d Debian Buster: Fix "No module named 'ipapython'".
When using ipaclient role under Debian 10 (Buster), it is required
that the python interpreter is set to Python 2.7 as freeipa-client
package pulls in Python 2.7 dependencies, and does not work with
Python 3.

This patch adds configuration to properly set python interpreter.

Based on the work by Marc Richter (@The-Judge on Github).

Fix issue #607.
2021-11-29 16:27:32 -03:00
Rafael Guterres Jeffman
c7a3b26ec4 Ubuntu 18.04: Fix role instalation for Ubuntu Bionic Beaver.
Ubuntu Bionic Beaver must use python2 as Python interpreter due to
the way python-ipalib package is defined.

Before using the role for installation, one  must install package
python2.7 before executing this role.

Based on the work by Manuel Laurent (@mlaurent205 on Github).
2021-11-29 16:24:20 -03:00
Rafael Guterres Jeffman
2f9791f6c5 DNSZone: Use IPAAnsibleModule.
As FreeIPABaseModule is deprecated, change DNSZone to use
IPAAnsibleModule.
2021-11-16 15:51:55 -03:00
Rafael Guterres Jeffman
f41104520e automountlocation: Use IPAAnsibleModule.
As FreeIPABaseModule is deprecated, change AutomountLocation to use
IPAAnsibleModule.
2021-11-16 15:51:55 -03:00
Rafael Guterres Jeffman
d81994475e Deprecate FreeIPABaseModule in favor of IPAAnsibleModule.
This patch add several deprecate warnings to FreeIPABaseModule, and
creates adapters to ease conversion of client classes to
IPAAnsibleModule.

There is no 'ipa_commands' management in IPAAnsibleModule, as 'command's
is a list of tuples containing '(command, name, args)', and should be
managed by the module itself. Commands with no arguments should use an
empty dictionary as 'args'.

The 'ipa_run' method should be replaced by:

```
exit_args = {}
ipaapi_context = self.params_get("ipaapi_context")
with self.ipa_connect(context=ipaapi_context):
    self.check_ipa_params()
    self.define_ipa_commands()
    changed = self.execute_ipa_commands(
                self.ipa_commands,
                result_handler=my_custom_handler,
                exit_args=exit_args
            )
self.exit_json(changed=changed, **exit_args)
```

The 'process_command_result' method should be changed to a result
handler:

```
def my_result_handler(self, result, command, name, args, exit_args):
    """Process command result.""'
```

Use of 'ipa_params' should be replaced by IPAAnsibleModule.params_get.
If 'get_ipa_command_args' is used, then the mapping can be created with
class IPAParamMapping (formelly AnsibleFreeIPAParams), which also
enables the same property-like usage of 'ipa_params':

```
param_mapping = IPAParamMapping(module, mapping)
```

The goal is to have all ansible-freeipa modules using the same codebase,
reducing code duplication, and allowing better object composition, for
example, with the IPAParamMapping class.
2021-11-16 15:51:55 -03:00
42 changed files with 1634 additions and 624 deletions

View File

@@ -104,13 +104,160 @@ Example playbook to add an inclusive condition to an existing rule
ipaadmin_password: SomeADMINpassword
name: "My domain hosts"
description: "my automember condition"
automember_tye: hostgroup
automember_type: hostgroup
action: member
inclusive:
- key: fqdn
expression: ".*.mydomain.com"
```
Example playbook to ensure group membership for all users has been rebuilt
```yaml
- name: Playbook to ensure group membership for all users has been rebuilt
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
state: rebuilt
```
Example playbook to ensure group membership for given users has been rebuilt
```yaml
- name: Playbook to ensure group membership for given users has been rebuilt
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
users:
- user1
- user2
state: rebuilt
```
Example playbook to ensure hostgroup membership for all hosts has been rebuilt
```yaml
- name: Playbook to ensure hostgroup membership for all hosts has been rebuilt
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
state: rebuilt
```
Example playbook to ensure hostgroup membership for given hosts has been rebuilt
```yaml
- name: Playbook to ensure hostgroup membership for given hosts has been rebuilt
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
hosts:
- host1.mydomain.com
- host2.mydomain.com
state: rebuilt
```
Example playbook to ensure default group fallback_group for all unmatched group entries is set
```yaml
- name: Playbook to ensure default group fallback_group for all unmatched group entries is set
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
default_group: fallback_group
```
Example playbook to ensure default group for all unmatched group entries is not set
```yaml
- name: Playbook to ensure default group for all unmatched group entries is not set
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
default_group: ""
automember_type: group
state: absent
```
Example playbook to ensure default hostgroup fallback_hostgroup for all unmatched group entries
```yaml
- name: Playbook to ensure default hostgroup fallback_hostgroup for all unmatched group entries
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
default_group: fallback_hostgroup
```
Example playbook to ensure default hostgroup for all unmatched group entries is not set
```yaml
- name: Playbook to ensure default hostgroup for all unmatched group entries is not set
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
default_group: ""
state: absent
```
Example playbook to ensure all orphan automember group rules are removed:
```yaml
- name: Playbook to ensure all orphan automember group rules are removed
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
state: orphans_removed
```
Example playbook to ensure all orphan automember hostgroup rules are removed:
```yaml
- name: Playbook to ensure all orphan automember hostgroup rules are removed
hosts: ipaserver
become: yes
gather_facts: no
tasks:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
state: orphans_removed
```
Variables
---------
@@ -129,11 +276,16 @@ Variable | Description | Required
`automember_type` | Grouping to which the rule applies. It can be one of `group`, `hostgroup`. | yes
`inclusive` | List of dictionaries in the format of `{'key': attribute, 'expression': inclusive_regex}` | no
`exclusive` | List of dictionaries in the format of `{'key': attribute, 'expression': exclusive_regex}` | no
`users` | Users to rebuild membership for. | no
`hosts` | Hosts to rebuild membership for. | no
`no_wait` | Don't wait for rebuilding membership. | no
`default_group` | Default (fallback) group for all unmatched entries. Use the empty string "" for ensuring the default group is not set. | no
`action` | Work on automember or member level. It can be one of `member` or `automember` and defaults to `automember`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, default: `present`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, 'rebuilt'. 'orphans_removed' default: `present`. | no
Authors
=======
Mark Hahl
Thomas Woerner

View File

@@ -56,12 +56,12 @@ Example playbook to read config options:
register: result
- name: display default login shell
debug:
msg: '{{ result.config.defaultlogin }}'
msg: '{{ result.config.defaultshell }}'
- name: ensure defaultloginshell and maxusernamelength are set as required
ipaconfig:
ipaadmin_password: password
defaultlogin: /bin/bash
defaultshell: /bin/bash
maxusername: 64
```

View File

@@ -81,7 +81,7 @@ Example playbook to ensure maxlife is set to 49 in global policy:
become: true
tasks:
# Ensure absence of pwpolicies for group ops
# Ensure maxlife is set to 49 in global policy
- ipapwpolicy:
ipaadmin_password: SomeADMINpassword
maxlife: 49

View File

@@ -0,0 +1,30 @@
FROM quay.io/centos/centos:stream9
ENV container=docker
RUN rm -fv /var/cache/dnf/metadata_lock.pid; \
dnf makecache; \
dnf --assumeyes install \
/usr/bin/python3 \
/usr/bin/python3-config \
/usr/bin/dnf-3 \
sudo \
bash \
systemd \
procps-ng \
iproute && \
dnf clean all; \
(cd /lib/systemd/system/sysinit.target.wants/; for i in *; do [ $i == systemd-tmpfiles-setup.service ] || rm -f $i; done); \
rm -f /lib/systemd/system/multi-user.target.wants/*;\
rm -f /etc/systemd/system/*.wants/*;\
rm -f /lib/systemd/system/local-fs.target.wants/*; \
rm -f /lib/systemd/system/sockets.target.wants/*udev*; \
rm -f /lib/systemd/system/sockets.target.wants/*initctl*; \
rm -f /lib/systemd/system/basic.target.wants/*;\
rm -f /lib/systemd/system/anaconda.target.wants/*; \
rm -rf /var/cache/dnf/;
STOPSIGNAL RTMIN+3
VOLUME ["/sys/fs/cgroup"]
CMD ["/usr/sbin/init"]

View File

@@ -0,0 +1,18 @@
---
driver:
name: docker
platforms:
- name: centos-9-build
image: "quay.io/centos/centos:stream9"
dockerfile: Dockerfile
hostname: ipaserver.test.local
dns_servers:
- 8.8.8.8
volumes:
- /sys/fs/cgroup:/sys/fs/cgroup:ro
command: /usr/sbin/init
privileged: true
provisioner:
name: ansible
playbooks:
prepare: ../resources/playbooks/prepare-build.yml

View File

@@ -0,0 +1,10 @@
---
- name: Automember default group not set
hosts: ipaserver
become: true
tasks:
- name: Ensure automember default group is not set
ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
default_group: ""

View File

@@ -0,0 +1,10 @@
---
- name: Automember default group set
hosts: ipaserver
become: true
tasks:
- name: Ensure automember default group is set
ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
default_group: fallback_group

View File

@@ -0,0 +1,10 @@
---
- name: Automember default hostgroup not set
hosts: ipaserver
become: true
tasks:
- name: Ensure automember default hostgroup is not set
ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
default_group: ""

View File

@@ -0,0 +1,10 @@
---
- name: Automember default hostgroup set
hosts: ipaserver
become: true
tasks:
- name: Ensure automember default hostgroup is set
ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
default_group: fallback_hostgroup

View File

@@ -0,0 +1,10 @@
---
- name: Automember group membership for all users rebuilt example
hosts: ipaserver
become: true
tasks:
- name: Ensure group automember rule admins is present
ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
state: rebuilt

View File

@@ -0,0 +1,12 @@
---
- name: Automember group membership for given users rebuilt example
hosts: ipaserver
become: true
tasks:
- name: Ensure group membership for given users has been rebuilt
ipaautomember:
ipaadmin_password: SomeADMINpassword
users:
- user1
- user2
state: rebuilt

View File

@@ -0,0 +1,10 @@
---
- name: Automember orphan group rules are removed example
hosts: ipaserver
become: true
tasks:
- name: Ensure orphan group rules are removed
ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
state: orphans_removed

View File

@@ -0,0 +1,10 @@
---
- name: Automember hostgroup membership for all hosts rebuilt example
hosts: ipaserver
become: true
tasks:
- name: Ensure hostgroup membership for all hosts has been rebuilt
ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
state: rebuilt

View File

@@ -0,0 +1,12 @@
---
- name: Automember hostgroup membership for given hosts rebuilt example
hosts: ipaserver
become: true
tasks:
- name: Ensure hostgroup membership for given hosts has been rebuilt
ipaautomember:
ipaadmin_password: SomeADMINpassword
hosts:
- host1.mydomain.com
- host2.mydomain.com
state: rebuilt

View File

@@ -0,0 +1,10 @@
---
- name: Automember orphan hostgroup rules are removed example
hosts: ipaserver
become: true
tasks:
- name: Ensure orphan hostgroup rules are removed
ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
state: orphans_removed

View File

@@ -419,6 +419,9 @@ else:
def api_get_realm():
return api.env.realm
def api_get_basedn():
return api.env.basedn
def gen_add_del_lists(user_list, res_list):
"""
Generate the lists for the addition and removal of members.
@@ -566,10 +569,76 @@ else:
print(jsonify(kwargs))
sys.exit(0)
class AnsibleFreeIPAParams(Mapping):
def __init__(self, ansible_module):
class IPAParamMapping(Mapping):
"""
Provides IPA API mapping to playbook parameters or computed values.
It can be used to define a mapping of playbook parameters
or methods that provide computed values to IPA API arguments.
Playbook parameters can be retrieved as properties,
and the set of IPA arguments for a command can be
retrived with ``get_ipa_command_args()``. The keys for
``param_mapping`` are also the keys of the argument set.
The values of ``param_mapping`` can be either:
* a str representing a key of ``AnsibleModule.params``.
* a callable.
In case of an ``AnsibleModule.param`` the value of the playbook
param will be used for that argument. If it is a ``callable``,
the value returned by the execution of it will be used.
Example:
-------
def check_params(ipa_params):
# Module parameters can be accessed as properties.
if len(ipa_params.name) == 0:
ipa_params.ansible_module.fail_json(msg="No given name.")
def define_ipa_commands(self):
# Create the argument dict from the defined mapping.
args = self.get_ipa_command_args()
_commands = [("obj-name", "some_ipa_command", args)]
return _commands
def a_method_for_a_computed_param():
return "Some computed value"
def main():
ansible_module = SomeIPAModule(argument_spec=dict(
name=dict(type="list", aliases=["cn"], required=True),
state=dict(type="str", default="present",
choices=["present", "absent"]),
module_param=(type="str", required=False),
)
)
# Define the playbook to IPA API mapping
ipa_param_mapping = {
"arg_to_be_passed_to_ipa_command": "module_param",
"another_arg": a_method_for_a_computed_param,
}
ipa_params = IPAParamMapping(
ansible_module,
param_mapping=ipa_param_mapping
)
check_params(ipa_params)
comands = define_ipa_commands(ipa_params)
ansible_module.execute_ipa_commands(commands)
"""
def __init__(self, ansible_module, param_mapping=None):
self.mapping = ansible_module.params
self.ansible_module = ansible_module
self.param_mapping = param_mapping or {}
def __getitem__(self, key):
param = self.mapping[key]
@@ -590,6 +659,36 @@ else:
def __getattr__(self, name):
return self.get(name)
def get_ipa_command_args(self, **kwargs):
"""Return a dict to be passed to an IPA command."""
args = {}
for ipa_param_name, param_name in self.param_mapping.items():
# Check if param_name is actually a param
if param_name in self.ansible_module.params:
value = self.ansible_module.params_get(param_name)
if isinstance(value, bool):
value = "TRUE" if value else "FALSE"
# Since param wasn't a param check if it's a method name
elif callable(param_name):
value = param_name(**kwargs)
# We don't have a way to guess the value so fail.
else:
self.ansible_module.fail_json(
msg=(
"Couldn't get a value for '%s'. Option '%s' is "
"not a module argument neither a defined method."
)
% (ipa_param_name, param_name)
)
if value is not None:
args[ipa_param_name] = value
return args
class IPAAnsibleModule(AnsibleModule):
"""
IPA Ansible Module.
@@ -786,6 +885,11 @@ else:
"""Retrieve IPA API realm."""
return api_get_realm()
@staticmethod
def ipa_get_basedn():
"""Retrieve IPA API basedn."""
return api_get_basedn()
@staticmethod
def ipa_command_exists(command):
"""
@@ -1034,6 +1138,11 @@ else:
# pylint: disable=super-with-arguments
super(FreeIPABaseModule, self).__init__(*args, **kwargs)
self.deprecate(
msg="FreeIPABaseModule is deprecated. Use IPAAnsibleModule.",
version="1.5.0"
)
# Status of an execution. Will be changed to True
# if something is actually peformed.
self.changed = False
@@ -1049,11 +1158,6 @@ else:
# Module exit arguments.
self.exit_args = {}
# Wrapper around the AnsibleModule.params.
# Return the actual params but performing transformations
# when needed.
self.ipa_params = AnsibleFreeIPAParams(self)
def get_ipa_command_args(self, **kwargs):
"""
Return a dict to be passed to an IPA command.
@@ -1074,97 +1178,77 @@ else:
server).
"""
args = {}
for ipa_param_name, param_name in self.ipa_param_mapping.items():
# Check if param_name is actually a param
if param_name in self.ipa_params:
value = self.ipa_params.get(param_name)
if isinstance(value, bool):
value = "TRUE" if value else "FALSE"
# Since param wasn't a param check if it's a method name
elif hasattr(self, param_name):
method = getattr(self, param_name)
if callable(method):
value = method(**kwargs)
# We don't have a way to guess the value so fail.
else:
self.fail_json(
msg=(
"Couldn't get a value for '%s'. Option '%s' is "
"not a module argument neither a defined method."
)
% (ipa_param_name, param_name)
)
if value is not None:
args[ipa_param_name] = value
return args
self.deprecate(
msg=(
"FreeIPABaseModule is deprecated. Use IPAAnsibleModule. "
"Use 'AnsibleFreeIPAParams.get_ipa_command_args()', "
"Instantiate it using the class 'ipa_params_mapping'."
),
version="1.5.0"
)
mapping = IPAParamMapping(self, self.ipa_param_mapping)
return mapping.get_ipa_command_args(**kwargs)
def check_ipa_params(self):
"""Validate ipa_params before command is called."""
self.deprecate(
msg=(
"FreeIPABaseModule is deprecated. Use IPAAnsibleModule. "
),
version="1.5.0"
)
pass # pylint: disable=unnecessary-pass
def define_ipa_commands(self):
"""Define commands that will be run in IPA server."""
raise NotImplementedError
def get_command_errors(self, command, result):
"""Look for erros into command results."""
# Get all errors
# All "already a member" and "not a member" failures in the
# result are ignored. All others are reported.
errors = []
for item in result.get("failed", tuple()):
failed_item = result["failed"][item]
for member_type in failed_item:
for member, failure in failed_item[member_type]:
if (
"already a member" in failure
or "not a member" in failure
):
continue
errors.append(
"%s: %s %s: %s"
% (command, member_type, member, failure)
)
if len(errors) > 0:
self.fail_json(", ".join("errors")) # pylint: disable=E1121
def add_ipa_command(self, command, name=None, args=None):
"""Add a command to the list of commands to be executed."""
self.ipa_commands.append((name, command, args or {}))
def _run_ipa_commands(self):
"""Execute commands in self.ipa_commands."""
if self.check_mode:
self.changed = len(self.ipa_commands) > 0
return
self.changed = self.execute_ipa_commands(
self.ipa_commands,
result_handler=self.process_results.__func__,
exit_args=self.exit_args
)
result = None
def process_results(
self, result, command, name, args, exit_args
): # pylint: disable=unused-argument
"""
Process an API command result.
for name, command, args in self.ipa_commands:
try:
result = self.ipa_command(command, name, args)
except Exception as excpt:
self.fail_json(msg="%s: %s: %s" % (command, name,
str(excpt)))
else:
self.process_command_result(name, command, args, result)
self.get_command_errors(command, result)
This method must be overriden in subclasses if 'exit_args'
is to be modified.
"""
self.deprecate(
msg=(
"FreeIPABaseModule is deprecated. Use IPAAnsibleModule. "
),
version="1.5.0"
)
self.process_command_result(name, command, args, result)
def process_command_result(self, _name, _command, _args, result):
"""
Process an API command result.
This method can be overriden in subclasses, and
change self.exit_values
to return data in the result for the controller.
change self.exit_values to return data in the
result for the controller.
"""
self.deprecate(
msg=(
"FreeIPABaseModule is deprecated. Use IPAAnsibleModule. "
"To aid in porting to IPAAnsibleModule, change to "
"'FreeIPABaseModule.process_results'."
),
version="1.5.0"
)
if "completed" in result:
if result["completed"] > 0:
self.changed = True
@@ -1178,12 +1262,26 @@ else:
Returns True in case current IPA object attributes differ from
args passed to the module.
"""
self.deprecate(
msg=(
"FreeIPABaseModule is deprecated. Use IPAAnsibleModule. "
"FreeIPABaseModule require_ipa_attrs_change() is "
"deprecated. Use ansible_freeipa_module.compare_args()."
),
version="1.5.0"
)
equal = compare_args_ipa(self, command_args, ipa_attrs)
return not equal
def ipa_run(self):
"""Execute module actions."""
ipaapi_context = self.ipa_params.get("ipaapi_context")
self.deprecate(
msg=(
"FreeIPABaseModule is deprecated. Use IPAAnsibleModule."
),
version="1.5.0"
)
ipaapi_context = self.params_get("ipaapi_context")
with self.ipa_connect(context=ipaapi_context):
self.check_ipa_params()
self.define_ipa_commands()

View File

@@ -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.

View File

@@ -79,6 +79,20 @@ options:
description: The expression of the regex
type: str
required: true
users:
description: Users to rebuild membership for.
type: list
required: false
hosts:
description: Hosts to rebuild membership for.
type: list
required: false
no_wait:
description: Don't wait for rebuilding membership.
type: bool
default_group:
description: Default (fallback) group for all unmatched entries.
type: str
action:
description: Work on automember or member level
default: automember
@@ -86,10 +100,11 @@ options:
state:
description: State to ensure
default: present
choices: ["present", "absent"]
choices: ["present", "absent", "rebuilt", "orphans_removed"]
author:
- Mark Hahl
- Jake Reynolds
- Thomas Woerner
"""
EXAMPLES = """
@@ -116,12 +131,78 @@ EXAMPLES = """
- ipaautomember:
ipaadmin_password: SomeADMINpassword
name: "My domain hosts"
automember_tye: hostgroup
automember_type: hostgroup
action: member
inclusive:
- key: fqdn
expression: ".*.mydomain.com"
# Ensure group membership for all users has been rebuilt
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
state: rebuilt
# Ensure group membership for given users has been rebuilt
- ipaautomember:
ipaadmin_password: SomeADMINpassword
users:
- user1
- user2
state: rebuilt
# Ensure hostgroup membership for all hosts has been rebuilt
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
state: rebuilt
# Ensure hostgroup membership for given hosts has been rebuilt
- ipaautomember:
ipaadmin_password: SomeADMINpassword
hosts:
- host1.mydomain.com
- host2.mydomain.com
state: rebuilt
# Ensure default group fallback_group for all unmatched group entries is set
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
default_group: fallback_group
# Ensure default group for all unmatched group entries is not set
- ipaautomember:
ipaadmin_password: SomeADMINpassword
default_group: ""
automember_type: group
state: absent
# Ensure default hostgroup fallback_hostgroup for all unmatched group entries
# is set
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
default_group: fallback_hostgroup
# Ensure default hostgroup for all unmatched group entries is not set
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
default_group: ""
state: absent
# Example playbook to ensure all orphan automember group rules are removed:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: group
state: orphans_removed
# Example playbook to ensure all orphan automember hostgroup rules are removed:
- ipaautomember:
ipaadmin_password: SomeADMINpassword
automember_type: hostgroup
state: orphans_removed
"""
RETURN = """
@@ -129,14 +210,14 @@ RETURN = """
from ansible.module_utils.ansible_freeipa_module import (
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, ipalib_errors
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, ipalib_errors, DN
)
def find_automember(module, name, grouping):
def find_automember(module, name, automember_type):
_args = {
"all": True,
"type": grouping
"type": automember_type
}
try:
@@ -146,13 +227,40 @@ def find_automember(module, name, grouping):
return _result["result"]
def gen_condition_args(grouping,
def find_automember_orphans(module, automember_type):
_args = {
"all": True,
"type": automember_type
}
try:
_result = module.ipa_command_no_name("automember_find_orphans", _args)
except ipalib_errors.NotFound:
return None
return _result
def find_automember_default_group(module, automember_type):
_args = {
"all": True,
"type": automember_type
}
try:
_result = module.ipa_command_no_name("automember_default_group_show",
_args)
except ipalib_errors.NotFound:
return None
return _result["result"]
def gen_condition_args(automember_type,
key,
inclusiveregex=None,
exclusiveregex=None):
_args = {}
if grouping is not None:
_args['type'] = grouping
if automember_type is not None:
_args['type'] = automember_type
if key is not None:
_args['key'] = key
if inclusiveregex is not None:
@@ -163,13 +271,23 @@ def gen_condition_args(grouping,
return _args
def gen_args(description, grouping):
def gen_rebuild_args(automember_type, rebuild_users, rebuild_hosts, no_wait):
_args = {"no_wait": no_wait}
if automember_type is not None:
_args['type'] = automember_type
if rebuild_users is not None:
_args["users"] = rebuild_users
if rebuild_hosts is not None:
_args["hosts"] = rebuild_hosts
return _args
def gen_args(description, automember_type):
_args = {}
if description is not None:
_args["description"] = description
if grouping is not None:
_args['type'] = grouping
if automember_type is not None:
_args['type'] = automember_type
return _args
@@ -194,28 +312,35 @@ def main():
argument_spec=dict(
# general
inclusive=dict(type="list",
aliases=["automemberinclusiveregex"], default=None,
aliases=["automemberinclusiveregex"],
default=None,
options=dict(
key=dict(type="str", required=True),
expression=dict(type="str", required=True)
),
elements="dict", required=False),
exclusive=dict(type="list", aliases=[
"automemberexclusiveregex"], default=None,
elements="dict",
required=False),
exclusive=dict(type="list",
aliases=["automemberexclusiveregex"],
default=None,
options=dict(
key=dict(type="str", required=True),
expression=dict(type="str", required=True)
),
elements="dict", required=False),
elements="dict",
required=False),
name=dict(type="list", aliases=["cn"],
default=None, required=True),
default=None, required=False),
description=dict(type="str", default=None),
automember_type=dict(type='str', required=False,
choices=['group', 'hostgroup']),
no_wait=dict(type="bool", default=None),
default_group=dict(type="str", default=None),
action=dict(type="str", default="automember",
choices=["member", "automember"]),
state=dict(type="str", default="present",
choices=["present", "absent", "rebuild"]),
choices=["present", "absent", "rebuilt",
"orphans_removed"]),
users=dict(type="list", default=None),
hosts=dict(type="list", default=None),
),
@@ -228,6 +353,8 @@ def main():
# general
names = ansible_module.params_get("name")
if names is None:
names = []
# present
description = ansible_module.params_get("description")
@@ -236,6 +363,12 @@ def main():
inclusive = ansible_module.params_get("inclusive")
exclusive = ansible_module.params_get("exclusive")
# no_wait for rebuilt
no_wait = ansible_module.params_get("no_wait")
# default_group
default_group = ansible_module.params_get("default_group")
# action
action = ansible_module.params_get("action")
# state
@@ -250,12 +383,51 @@ def main():
# Check parameters
invalid = []
if state != "rebuild":
invalid = ["rebuild_hosts", "rebuild_users"]
if state in ["rebuilt", "orphans_removed"]:
invalid = ["name", "description", "exclusive", "inclusive",
"default_group"]
if not automember_type and state != "rebuild":
if action == "member":
ansible_module.fail_json(
msg="'automember_type' is required unless state: rebuild")
msg="'action=member' is not usable with state '%s'" % state)
if state == "rebuilt":
if automember_type == "group" and rebuild_hosts is not None:
ansible_module.fail_json(
msg="state %s: hosts can not be set when type is '%s'" %
(state, automember_type))
if automember_type == "hostgroup" and rebuild_users is not None:
ansible_module.fail_json(
msg="state %s: users can not be set when type is '%s'" %
(state, automember_type))
elif state == "orphans_removed":
invalid.extend(["users", "hosts"])
if not automember_type:
ansible_module.fail_json(
msg="'automember_type' is required unless state: rebuilt")
else:
if default_group is not None:
for param in ["name", "exclusive", "inclusive", "users", "hosts"
"no_wait"]:
if ansible_module.params.get(param) is not None:
msg = "Cannot use {0} together with default_group"
ansible_module.fail_json(msg=msg.format(param))
if action == "member":
ansible_module.fail_json(
msg="Cannot use default_group with action:member")
if state == "absent":
ansible_module.fail_json(
msg="Cannot use default_group with state:absent")
else:
invalid = ["users", "hosts", "no_wait"]
if not automember_type:
ansible_module.fail_json(
msg="'automember_type' is required.")
ansible_module.params_fail_used_invalid(invalid, state, action)
@@ -392,16 +564,45 @@ def main():
'automember_remove_condition',
condition_args])
elif state == "rebuild":
if automember_type:
commands.append([None, 'automember_rebuild',
{"type": automember_type}])
if rebuild_users:
commands.append([None, 'automember_rebuild',
{"users": rebuild_users}])
if rebuild_hosts:
commands.append([None, 'automember_rebuild',
{"hosts": rebuild_hosts}])
if len(names) == 0:
if state == "rebuilt":
args = gen_rebuild_args(automember_type, rebuild_users,
rebuild_hosts, no_wait)
commands.append([None, 'automember_rebuild', args])
elif state == "orphans_removed":
res_find = find_automember_orphans(ansible_module,
automember_type)
if res_find["count"] > 0:
commands.append([None, 'automember_find_orphans',
{'type': automember_type,
'remove': True}])
elif default_group is not None and state == "present":
res_find = find_automember_default_group(ansible_module,
automember_type)
if default_group == "":
if isinstance(res_find["automemberdefaultgroup"], list):
commands.append([None,
'automember_default_group_remove',
{'type': automember_type}])
ansible_module.warn("commands: %s" % repr(commands))
else:
dn_default_group = [DN(('cn', default_group),
('cn', '%ss' % automember_type),
('cn', 'accounts'),
ansible_module.ipa_get_basedn())]
if repr(res_find["automemberdefaultgroup"]) != \
repr(dn_default_group):
commands.append(
[None, 'automember_default_group_set',
{'type': automember_type,
'automemberdefaultgroup': default_group}])
else:
ansible_module.fail_json(msg="Invalid operation")
# Execute commands

View File

@@ -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__":

View File

@@ -299,7 +299,7 @@ def main():
"KDC:Disable Last Success",
"KDC:Disable Lockout",
"KDC:Disable Default Preauth for SPNs",
""]), # noqa E128
""]), # noqa E128
selinuxusermaporder=dict(type="list", required=False,
aliases=['ipaselinuxusermaporder']),
selinuxusermapdefault=dict(type="str", required=False,
@@ -365,15 +365,15 @@ def main():
if params.get("ipadomainresolutionorder", None):
params["ipadomainresolutionorder"] = \
":".join(params["ipadomainresolutionorder"])
":".join(params["ipadomainresolutionorder"])
if params.get("ipausersearchfields", None):
params["ipausersearchfields"] = \
",".join(params["ipausersearchfields"])
",".join(params["ipausersearchfields"])
if params.get("ipagroupsearchfields", None):
params["ipagroupsearchfields"] = \
",".join(params["ipagroupsearchfields"])
",".join(params["ipagroupsearchfields"])
# verify limits on INT values.
args_with_limits = [
@@ -418,12 +418,12 @@ def main():
if ansible_module.argument_spec.get(k):
arg_type = ansible_module.argument_spec[k]['type']
if k in (
'ipaselinuxusermaporder', 'domain_resolution_order'
):
'ipaselinuxusermaporder', 'domain_resolution_order'
):
exit_args[k] = result.get(key)[0].split('$')
elif k in (
'usersearch', 'groupsearch'
):
'usersearch', 'groupsearch'
):
exit_args[k] = result.get(key)[0].split(',')
elif isinstance(value, str) and arg_type == "list":
exit_args[k] = [value]

View File

@@ -165,24 +165,23 @@ def gen_args(module, state, dnsconfig, forwarders, forward_policy,
def main():
forwarder_spec = dict(
ip_address=dict(type=str, required=True),
port=dict(type=int, required=False, default=None)
ip_address=dict(type=str, required=True),
port=dict(type=int, required=False, default=None)
)
ansible_module = IPAAnsibleModule(
argument_spec=dict(
# dnsconfig
forwarders=dict(type='list', default=None, required=False,
options=dict(**forwarder_spec)),
forward_policy=dict(type='str', required=False, default=None,
choices=['only', 'first', 'none']),
allow_sync_ptr=dict(type='bool', required=False, default=None),
argument_spec=dict(
# dnsconfig
forwarders=dict(type='list', default=None, required=False,
options=dict(**forwarder_spec)),
forward_policy=dict(type='str', required=False, default=None,
choices=['only', 'first', 'none']),
allow_sync_ptr=dict(type='bool', required=False, default=None),
# general
state=dict(type="str", default="present",
choices=["present", "absent"]),
)
# general
state=dict(type="str", default="present",
choices=["present", "absent"]),
)
)
ansible_module._ansible_debug = True

View File

@@ -1108,12 +1108,13 @@ def configure_module():
name=dict(type="list", aliases=["record_name"], default=None,
required=False),
records=dict(type="list", default=None,
records=dict(type="list",
default=None,
options=dict(
# Here name is a simple string
name=dict(type='str', required=True,
aliases=['record_name']),
**record_spec),
# Here name is a simple string
name=dict(type='str', required=True,
aliases=['record_name']),
**record_spec),
),
# general

View File

@@ -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__":

View File

@@ -393,13 +393,13 @@ def main():
# If yes: modify
# Also if it is a modification from nonposix to posix
# or nonposix to external.
if not compare_args_ipa(ansible_module, args,
res_find) or \
(
not is_posix_group(res_find) and
not is_external_group(res_find) and
(posix or external)
):
if not compare_args_ipa(
ansible_module, args, res_find
) or (
not is_posix_group(res_find) and
not is_external_group(res_find) and
(posix or external)
):
if posix:
args['posix'] = True
if external:

View File

@@ -199,7 +199,7 @@ def ensure_absent_state(module, name, action, res_find):
member_args = {}
for key in ['user', 'group', 'host', 'hostgroup']:
items = member_intersect(
module, key, 'member_%s' % key, res_find)
module, key, 'member_%s' % key, res_find)
if items:
member_args[key] = items
@@ -298,7 +298,7 @@ def ensure_members_are_present(module, name, res_find):
member_args = {}
for key in ['user', 'group', 'host', 'hostgroup']:
items = member_difference(
module, key, 'member_%s' % key, res_find)
module, key, 'member_%s' % key, res_find)
if items:
member_args[key] = items

View File

@@ -289,10 +289,7 @@ def gen_args_smb(netbiosname, ok_as_delegate, ok_to_auth_as_delegate):
return _args
def check_parameters(module, state, action, names, parameters):
if not isinstance(parameters, dict):
raise AssertionError("parameters is not a dict")
def check_parameters(module, state, action, names):
# invalid parameters for everything but state 'present', action 'service'.
invalid = ['pac_type', 'auth_ind', 'skip_host_check',
'force', 'requires_pre_auth', 'ok_as_delegate',
@@ -314,8 +311,8 @@ def check_parameters(module, state, action, names, parameters):
invalid = ['delete_continue']
if (
not parameters.get('smb', False)
and parameters.get('netbiosname')
not module.params_get('smb')
and module.params_get('netbiosname')
):
module.fail_json(
msg="Argument 'netbiosname' can not be used without "
@@ -437,23 +434,6 @@ def main():
host = ansible_module.params_get("host")
allow_create_keytab_user = ansible_module.params_get(
"allow_create_keytab_user")
allow_create_keytab_group = ansible_module.params_get(
"allow_create_keytab_group")
allow_create_keytab_host = ansible_module.params_get(
"allow_create_keytab_host")
allow_create_keytab_hostgroup = ansible_module.params_get(
"allow_create_keytab_hostgroup")
allow_retrieve_keytab_user = ansible_module.params_get(
"allow_retrieve_keytab_user")
allow_retrieve_keytab_group = ansible_module.params_get(
"allow_retrieve_keytab_group")
allow_retrieve_keytab_host = ansible_module.params_get(
"allow_retrieve_keytab_host")
allow_retrieve_keytab_hostgroup = ansible_module.params_get(
"allow_retrieve_keytab_hostgroup")
delete_continue = ansible_module.params_get("delete_continue")
# action
@@ -462,7 +442,7 @@ def main():
state = ansible_module.params_get("state")
# check parameters
check_parameters(ansible_module, state, action, names, vars())
check_parameters(ansible_module, state, action, names)
# Init
@@ -479,11 +459,26 @@ def main():
msg="Skipping host check is not supported by your IPA version")
commands = []
keytab_members = ["user", "group", "host", "hostgroup"]
for name in names:
res_find = find_service(ansible_module, name)
res_principals = []
keytab = {
"retrieve": {
"allow": {k: [] for k in keytab_members},
"disallow": {k: [] for k in keytab_members},
},
"create": {
"allow": {k: [] for k in keytab_members},
"disallow": {k: [] for k in keytab_members},
},
}
certificate_add, certificate_del = [], []
host_add, host_del = [], []
principal_add, principal_del = [], []
if principal and res_find:
# When comparing principals to the existing ones,
# the REALM is needded, and are added here for those
@@ -534,37 +529,8 @@ def main():
if res_find is None:
commands.append([name, 'service_add', args])
certificate_add = certificate or []
certificate_del = []
host_add = host or []
host_del = []
principal_add = principal or []
principal_del = []
allow_create_keytab_user_add = \
allow_create_keytab_user or []
allow_create_keytab_user_del = []
allow_create_keytab_group_add = \
allow_create_keytab_group or []
allow_create_keytab_group_del = []
allow_create_keytab_host_add = \
allow_create_keytab_host or []
allow_create_keytab_host_del = []
allow_create_keytab_hostgroup_add = \
allow_create_keytab_hostgroup or []
allow_create_keytab_hostgroup_del = []
allow_retrieve_keytab_user_add = \
allow_retrieve_keytab_user or []
allow_retrieve_keytab_user_del = []
allow_retrieve_keytab_group_add = \
allow_retrieve_keytab_group or []
allow_retrieve_keytab_group_del = []
allow_retrieve_keytab_host_add = \
allow_retrieve_keytab_host or []
allow_retrieve_keytab_host_del = []
allow_retrieve_keytab_hostgroup_add = \
allow_retrieve_keytab_hostgroup or []
allow_retrieve_keytab_hostgroup_del = []
# Use an empty res_find to manage members
res_find = {}
else:
for remove in ['skip_host_check', 'force']:
@@ -577,75 +543,22 @@ def main():
args.get("krbprincipalauthind", [""]) ==
res_find.get("krbprincipalauthind", [""])
)
):
):
del args["krbprincipalauthind"]
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([name, "service_mod", args])
certificate_add, certificate_del = gen_add_del_lists(
certificate, res_find.get("usercertificate"))
# Manage members
certificate_add, certificate_del = gen_add_del_lists(
certificate, res_find.get("usercertificate"))
host_add, host_del = gen_add_del_lists(
host, res_find.get('managedby_host', []))
host_add, host_del = gen_add_del_lists(
host, res_find.get('managedby_host'))
principal_add, principal_del = \
gen_add_del_lists(principal, res_principals)
(allow_create_keytab_user_add,
allow_create_keytab_user_del) = \
gen_add_del_lists(
allow_create_keytab_user, res_find.get(
'ipaallowedtoperform_write_keys_user',
[]))
(allow_retrieve_keytab_user_add,
allow_retrieve_keytab_user_del) = \
gen_add_del_lists(
allow_retrieve_keytab_user, res_find.get(
'ipaallowedtoperform_read_keys_user',
[]))
(allow_create_keytab_group_add,
allow_create_keytab_group_del) = \
gen_add_del_lists(
allow_create_keytab_group, res_find.get(
'ipaallowedtoperform_write_keys_group',
[]))
(allow_retrieve_keytab_group_add,
allow_retrieve_keytab_group_del) = \
gen_add_del_lists(
allow_retrieve_keytab_group,
res_find.get(
'ipaallowedtoperform_read_keys_group',
[]))
(allow_create_keytab_host_add,
allow_create_keytab_host_del) = \
gen_add_del_lists(
allow_create_keytab_host,
res_find.get(
'ipaallowedtoperform_write_keys_host',
[]))
(allow_retrieve_keytab_host_add,
allow_retrieve_keytab_host_del) = \
gen_add_del_lists(
allow_retrieve_keytab_host,
res_find.get(
'ipaallowedtoperform_read_keys_host',
[]))
(allow_create_keytab_hostgroup_add,
allow_create_keytab_hostgroup_del) = \
gen_add_del_lists(
allow_create_keytab_hostgroup,
res_find.get(
'ipaallowedtoperform_write_keys_hostgroup',
[]))
(allow_retrieve_keytab_hostgroup_add,
allow_retrieve_keytab_hostgroup_del) = \
gen_add_del_lists(
allow_retrieve_keytab_hostgroup,
res_find.get(
'ipaallowedtoperform_read_keys_hostgroup',
[]))
principal_add, principal_del = gen_add_del_lists(
principal, res_principals)
elif action == "member":
if res_find is None:
@@ -653,137 +566,31 @@ def main():
certificate_add = gen_add_list(
certificate, res_find.get("usercertificate"))
certificate_del = []
host_add = gen_add_list(
host, res_find.get("managedby_host"))
host_del = []
host, res_find.get('managedby_host'))
principal_add = gen_add_list(principal, res_principals)
principal_del = []
allow_create_keytab_user_add = gen_add_list(
allow_create_keytab_user,
res_find.get("ipaallowedtoperform_write_keys_user")
)
allow_create_keytab_user_del = []
allow_create_keytab_group_add = gen_add_list(
allow_create_keytab_group,
res_find.get("ipaallowedtoperform_write_keys_group")
)
allow_create_keytab_group_del = []
allow_create_keytab_host_add = gen_add_list(
allow_create_keytab_host,
res_find.get("ipaallowedtoperform_write_keys_host")
)
allow_create_keytab_host_del = []
allow_create_keytab_hostgroup_add = gen_add_list(
allow_create_keytab_hostgroup,
res_find.get(
"ipaallowedtoperform_write_keys_hostgroup")
)
allow_create_keytab_hostgroup_del = []
allow_retrieve_keytab_user_add = gen_add_list(
allow_retrieve_keytab_user,
res_find.get("ipaallowedtoperform_read_keys_user")
)
allow_retrieve_keytab_user_del = []
allow_retrieve_keytab_group_add = gen_add_list(
allow_retrieve_keytab_group,
res_find.get("ipaallowedtoperform_read_keys_group")
)
allow_retrieve_keytab_group_del = []
allow_retrieve_keytab_host_add = gen_add_list(
allow_retrieve_keytab_host,
res_find.get("ipaallowedtoperform_read_keys_host")
)
allow_retrieve_keytab_host_del = []
allow_retrieve_keytab_hostgroup_add = gen_add_list(
allow_retrieve_keytab_hostgroup,
res_find.get("ipaallowedtoperform_read_keys_hostgroup")
)
allow_retrieve_keytab_hostgroup_del = []
if principal_add:
commands.append([name, "service_add_principal",
{"krbprincipalname": principal_add}])
if principal_del:
commands.append([name, "service_remove_principal",
{"krbprincipalname": principal_del}])
for _certificate in certificate_add:
commands.append([name, "service_add_cert",
{
"usercertificate":
_certificate,
}])
# Remove certificates
for _certificate in certificate_del:
commands.append([name, "service_remove_cert",
{
"usercertificate":
_certificate,
}])
# Add hosts.
if host is not None and len(host) > 0 and len(host_add) > 0:
commands.append([name, "service_add_host",
{"host": host_add}])
# Remove hosts
if host is not None and len(host) > 0 and len(host_del) > 0:
commands.append([name, "service_remove_host",
{"host": host_del}])
# Allow create keytab
if len(allow_create_keytab_user_add) > 0 or \
len(allow_create_keytab_group_add) > 0 or \
len(allow_create_keytab_host_add) > 0 or \
len(allow_create_keytab_hostgroup_add) > 0:
commands.append(
[name, "service_allow_create_keytab",
{'user': allow_create_keytab_user_add,
'group': allow_create_keytab_group_add,
'host': allow_create_keytab_host_add,
'hostgroup': allow_create_keytab_hostgroup_add
}])
# Disallow create keytab
if len(allow_create_keytab_user_del) > 0 or \
len(allow_create_keytab_group_del) > 0 or \
len(allow_create_keytab_host_del) > 0 or \
len(allow_create_keytab_hostgroup_del) > 0:
commands.append(
[name, "service_disallow_create_keytab",
{'user': allow_create_keytab_user_del,
'group': allow_create_keytab_group_del,
'host': allow_create_keytab_host_del,
'hostgroup': allow_create_keytab_hostgroup_del
}])
# Allow retrieve keytab
if len(allow_retrieve_keytab_user_add) > 0 or \
len(allow_retrieve_keytab_group_add) > 0 or \
len(allow_retrieve_keytab_host_add) > 0 or \
len(allow_retrieve_keytab_hostgroup_add) > 0:
commands.append(
[name, "service_allow_retrieve_keytab",
{'user': allow_retrieve_keytab_user_add,
'group': allow_retrieve_keytab_group_add,
'host': allow_retrieve_keytab_host_add,
'hostgroup': allow_retrieve_keytab_hostgroup_add
}])
# Disllow retrieve keytab
if len(allow_retrieve_keytab_user_del) > 0 or \
len(allow_retrieve_keytab_group_del) > 0 or \
len(allow_retrieve_keytab_host_del) > 0 or \
len(allow_retrieve_keytab_hostgroup_del) > 0:
commands.append(
[name, "service_disallow_retrieve_keytab",
{'user': allow_retrieve_keytab_user_del,
'group': allow_retrieve_keytab_group_del,
'host': allow_retrieve_keytab_host_del,
'hostgroup': allow_retrieve_keytab_hostgroup_del
}])
# get keytab management lists for any 'action'.
for perm in ["create", "retrieve"]:
oper = "write" if perm == "create" else "read"
for key in ["user", "group", "host", "hostgroup"]:
add_list, del_list = (
gen_add_del_lists(
ansible_module.params_get(
"allow_%s_keytab_%s" % (perm, key)
),
res_find.get(
'ipaallowedtoperform_%s_keys_%s'
% (oper, key)
)
)
)
keytab[perm]["allow"][key] = add_list
# Only remove members if action is 'service'
if action == "service":
keytab[perm]["disallow"][key] = del_list
elif state == "absent":
if action == "service":
@@ -795,97 +602,30 @@ def main():
if res_find is None:
ansible_module.fail_json(msg="No service '%s'" % name)
# Remove principals
principal_del = gen_intersection_list(
principal, res_principals)
if principal_del:
commands.append([name, "service_remove_principal",
{"krbprincipalname": principal_del}])
# Remove certificates
if certificate is not None:
existing = res_find.get('usercertificate', [])
for _certificate in certificate:
if _certificate in existing:
commands.append([name, "service_remove_cert",
{
"usercertificate":
_certificate,
}])
certificate_del = gen_intersection_list(
certificate, res_find.get("usercertificate"))
# Add hosts
host = gen_intersection_list(
host_del = gen_intersection_list(
host, res_find.get("managedby_host"))
if host is not None:
commands.append(
[name, "service_remove_host", {"host": host}])
allow_create_keytab_user_del = gen_intersection_list(
allow_create_keytab_user,
res_find.get("ipaallowedtoperform_write_keys_user")
)
allow_create_keytab_group_del = gen_intersection_list(
allow_create_keytab_group,
res_find.get("ipaallowedtoperform_write_keys_group")
)
allow_create_keytab_host_del = gen_intersection_list(
allow_create_keytab_host,
res_find.get("ipaallowedtoperform_write_keys_host")
)
allow_create_keytab_hostgroup_del = gen_intersection_list(
allow_create_keytab_hostgroup,
res_find.get(
"ipaallowedtoperform_write_keys_hostgroup")
)
# Allow create keytab
if any([
allow_create_keytab_user_del,
allow_create_keytab_group_del,
allow_create_keytab_host_del,
allow_create_keytab_hostgroup_del
]):
commands.append(
[name, "service_disallow_create_keytab",
{'user': allow_create_keytab_user_del,
'group': allow_create_keytab_group_del,
'host': allow_create_keytab_host_del,
'hostgroup': allow_create_keytab_hostgroup_del
}])
allow_retrieve_keytab_user_del = gen_intersection_list(
allow_retrieve_keytab_user,
res_find.get("ipaallowedtoperform_read_keys_user")
)
allow_retrieve_keytab_group_del = gen_intersection_list(
allow_retrieve_keytab_group,
res_find.get("ipaallowedtoperform_read_keys_group")
)
allow_retrieve_keytab_host_del = gen_intersection_list(
allow_retrieve_keytab_host,
res_find.get("ipaallowedtoperform_read_keys_host")
)
allow_retrieve_keytab_hostgroup_del = \
gen_intersection_list(
allow_retrieve_keytab_hostgroup,
res_find.get(
"ipaallowedtoperform_read_keys_hostgroup")
)
# Allow retriev keytab
if any([
allow_retrieve_keytab_user_del,
allow_retrieve_keytab_group_del,
allow_retrieve_keytab_host_del,
allow_retrieve_keytab_hostgroup_del
]):
commands.append(
[name, "service_disallow_retrieve_keytab",
{'user': allow_retrieve_keytab_user,
'group': allow_retrieve_keytab_group,
'host': allow_retrieve_keytab_host,
'hostgroup': allow_retrieve_keytab_hostgroup
}])
for perm in ["create", "retrieve"]:
oper = "write" if perm == "create" else "read"
for key in ["user", "group", "host", "hostgroup"]:
res_param = (
'ipaallowedtoperform_%s_keys_%s'
% (oper, key)
)
module_params = ansible_module.params_get(
"allow_%s_keytab_%s" % (perm, key)
)
existing = res_find.get(res_param)
del_list = (
gen_intersection_list(module_params, existing)
)
keytab[perm]["disallow"][key] = del_list
elif state == "disabled":
if action == "service":
@@ -898,9 +638,50 @@ def main():
ansible_module.fail_json(
msg="Invalid action '%s' for state '%s'" %
(action, state))
# Members are not managed when disabling service.
# Continue with next 'name'.
continue
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Manage members
if principal_add:
commands.append([name, "service_add_principal",
{"krbprincipalname": principal_add}])
if principal_del:
commands.append([name, "service_remove_principal",
{"krbprincipalname": principal_del}])
if certificate_add:
commands.append([name, "service_add_cert",
{"usercertificate": certificate_add}])
if certificate_del:
commands.append([name, "service_remove_cert",
{"usercertificate": certificate_del}])
if host_add:
commands.append([name, "service_add_host",
{"host": host_add}])
if host_del:
commands.append([name, "service_remove_host",
{"host": host_del}])
# manage keytab permissions.
for perm in ["create", "retrieve"]:
for mode in ["allow", "disallow"]:
for key in ["user", "group", "host", "hostgroup"]:
if keytab[perm][mode][key]:
commands.append([
name,
"service_%s_%s_keytab" % (mode, perm),
keytab[perm][mode]
])
break
# Check mode exit
if ansible_module.check_mode:
ansible_module.exit_json(changed=len(commands) > 0, **exit_args)
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, fail_on_member_errors=True)

View File

@@ -824,14 +824,17 @@ def main():
# general
name=dict(type="list", aliases=["login"], default=None,
required=False),
users=dict(type="list", aliases=["login"], default=None,
users=dict(type="list",
aliases=["login"],
default=None,
options=dict(
# Here name is a simple string
name=dict(type="str", required=True),
# Add user specific parameters
**user_spec
),
elements='dict', required=False),
elements='dict',
required=False),
# deleted
preserve=dict(required=False, type='bool', default=None),

View File

@@ -634,7 +634,7 @@ def main():
vault_password_file=dict(type="str", required=False, default=None,
no_log=False,
aliases=[
'password_file', "old_password_file"
'password_file', "old_password_file"
]),
new_password=dict(type="str", required=False, default=None,
no_log=True),

View File

@@ -45,7 +45,7 @@ EXAMPLES = '''
# Get IPA_BACKUP_DIR from ipaplatform
- name: Get IPA_BACKUP_DIR from ipaplatform
ipabackup_get_backup_dir:
register result
register: result
'''
RETURN = '''

View File

@@ -0,0 +1,10 @@
# vars/Ubuntu.yml
---
ipaserver_packages: [ "freeipa-server" ]
ipaserver_packages_dns: [ "freeipa-server-dns" ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ]
# Ubuntu Bionic Beaver must use python2 as Python interpreter due
# to the way python-ipalib package is defined.
# Package python2.7 must be installed before executing this role.
ansible_python_interpreter: '/usr/bin/python2.7'

View File

@@ -0,0 +1,7 @@
---
# vars/Debian.yml
ipaclient_packages: [ "freeipa-client" ]
# Debian Buster must use python2 as Python interpreter due
# to the way freeipa-client package is defined.
# You must install package python2.7 before executing this role.
ansible_python_interpreter: '/usr/bin/python2'

View File

@@ -0,0 +1,7 @@
# vars/Ubuntu-18.04.yml
---
ipaclient_packages: [ "freeipa-client" ]
# Ubuntu Bionic Beaver must use python2 as Python interpreter due
# to the way python-ipalib package is defined.
# Package python2.7 must be installed before executing this role.
ansible_python_interpreter: '/usr/bin/python2.7'

View File

@@ -0,0 +1,10 @@
# vars/Ubuntu.yml
---
ipareplica_packages: [ "freeipa-server" ]
ipareplica_packages_dns: [ "freeipa-server-dns" ]
ipareplica_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipareplica_packages_firewalld: [ "firewalld" ]
# Ubuntu Bionic Beaver must use python2 as Python interpreter due
# to the way python-ipalib package is defined.
# Package python2.7 must be installed before executing this role.
ansible_python_interpreter: '/usr/bin/python2.7'

View File

@@ -0,0 +1,10 @@
# vars/Ubuntu.yml
---
ipaserver_packages: [ "freeipa-server" ]
ipaserver_packages_dns: [ "freeipa-server-dns" ]
ipaserver_packages_adtrust: [ "freeipa-server-trust-ad" ]
ipaserver_packages_firewalld: [ "firewalld" ]
# Ubuntu Bionic Beaver must use python2 as Python interpreter due
# to the way python-ipalib package is defined.
# Package python2.7 must be installed before executing this role.
ansible_python_interpreter: '/usr/bin/python2.7'

View File

@@ -13,8 +13,8 @@
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: server
name: ThisShouldNotWork
state: rebuild
automember_type: group
state: rebuilt
register: result
failed_when: not (result.failed and result.msg is regex("No module named '*ipaserver'*"))
when: ipa_host_is_client

View File

@@ -0,0 +1,166 @@
---
- name: Test automember default groups
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: true
tasks:
# SET FACTS
# CLEANUP TEST ITEMS
- name: Ensure group testgroup is absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
state: absent
- name: Ensure hostgroup testhostgroup is absent
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
state: absent
- name: Ensure automember default group is unset
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: ""
automember_type: group
- name: Ensure automember default hostgroup is unset
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: ""
automember_type: hostgroup
# CREATE TEST ITEMS
- name: Ensure group testgroup is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
state: present
register: result
failed_when: not result.changed or result.failed
- name: Ensure hostgroup testhostgroup is present
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
state: present
register: result
failed_when: not result.changed or result.failed
# TESTS
# GROUP TEST
- name: Ensure automember default group is set to testgroup
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: testgroup
automember_type: group
register: result
failed_when: not result.changed or result.failed
- name: Ensure automember default group is set to testgroup, again
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: testgroup
automember_type: group
register: result
failed_when: result.changed or result.failed
- name: Ensure automember default group is unset
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: ""
automember_type: group
register: result
failed_when: not result.changed or result.failed
- name: Ensure automember default group is unset, again
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: ""
automember_type: group
register: result
failed_when: result.changed or result.failed
# HOSTGROUP TEST
- name: Ensure automember default hostgroup is set to testhostgroup
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: testhostgroup
automember_type: hostgroup
register: result
failed_when: not result.changed or result.failed
- name: Ensure automember default hostgroup is set to testhostgroup, again
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: testhostgroup
automember_type: hostgroup
register: result
failed_when: result.changed or result.failed
- name: Ensure automember default hostgroup is unset
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: ""
automember_type: hostgroup
register: result
failed_when: not result.changed or result.failed
- name: Ensure automember default hostgroup is unset, again
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: ""
automember_type: hostgroup
register: result
failed_when: result.changed or result.failed
# CLEANUP TEST ITEMS
- name: Ensure group testgroup is absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
state: absent
- name: Ensure hostgroup testhostgroup is absent
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
state: absent
- name: Ensure automember default group is unset
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: ""
automember_type: group
- name: Ensure automember default hostgroup is unset
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
default_group: ""
automember_type: hostgroup

View File

@@ -0,0 +1,250 @@
---
- name: Test automember orphans_removed
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: true
tasks:
# SET FACTS
- name: Get Domain from server name
set_fact:
ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] |
join ('.') }}"
when: ipaserver_domain is not defined
# CLEANUP TEST ITEMS
- name: Ensure user testuser is absent
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testuser
state: absent
- name: Ensure group testgroup is absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
state: absent
- name: Ensure host testhost is absent
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "{{ 'testhost.' + ipaserver_domain }}"
state: absent
- name: Ensure hostgroup testhostgroup is absent
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
state: absent
- name: Ensure automember group testgroup is absent
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
automember_type: group
state: absent
- name: Ensure automember hostgroup testhostgroup is absent
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
automember_type: hostgroup
state: absent
# CREATE TEST ITEMS
- name: Ensure user testuser is present
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testuser
first: Test
last: User
register: result
failed_when: not result.changed or result.failed
- name: Ensure host testhost is present
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "{{ 'testhost.' + ipaserver_domain }}"
force: yes
reverse: no
register: result
failed_when: not result.changed or result.failed
- name: Ensure group testgroup is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
state: present
register: result
failed_when: not result.changed or result.failed
- name: Ensure hostgroup testhostgroup is present
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
state: present
register: result
failed_when: not result.changed or result.failed
# TESTS
# GROUP TEST
- name: Ensure automember group testgroup exists
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
automember_type: group
register: result
failed_when: not result.changed or result.failed
- name: Ensure automember group condition exits for users
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
automember_type: group
action: member
inclusive:
- key: uid
expression: uid
register: result
failed_when: not result.changed or result.failed
- name: Ensure group testgroup is absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
state: absent
register: result
failed_when: not result.changed or result.failed
- name: Ensure group orphans have been removed
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: group
state: orphans_removed
register: result
failed_when: not result.changed or result.failed
- name: Ensure group orphans have been removed again
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: group
state: orphans_removed
register: result
failed_when: result.changed or result.failed
# HOSTGROUP TEST
- name: Ensure automember hostgroup testhostgroup exists
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
automember_type: hostgroup
register: result
failed_when: not result.changed or result.failed
- name: Ensure automember hostgroup condition exits for hosts
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
automember_type: hostgroup
action: member
inclusive:
- key: fqdn
expression: "{{ '.*.' + ipaserver_domain }}"
register: result
failed_when: not result.changed or result.failed
- name: Ensure hostgroup testhostgroup is absent
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
state: absent
register: result
failed_when: not result.changed or result.failed
- name: Ensure hostgroup orphans have been removed
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: hostgroup
state: orphans_removed
register: result
failed_when: not result.changed or result.failed
- name: Ensure hostgroup orphans have been removed again
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: hostgroup
state: orphans_removed
register: result
failed_when: result.changed or result.failed
# CLEANUP TEST ITEMS
- name: Ensure user testuser is absent
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testuser
state: absent
- name: Ensure group testgroup is absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
state: absent
- name: Ensure host testhost is absent
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "{{ 'testhost.' + ipaserver_domain }}"
state: absent
- name: Ensure hostgroup testhostgroup is absent
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
state: absent
- name: Ensure automember group testgroup is absent
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testgroup
automember_type: group
state: absent
- name: Ensure automember hostgroup testhostgroup is absent
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testhostgroup
automember_type: hostgroup
state: absent

View File

@@ -0,0 +1,155 @@
---
- name: Test automember rebuilt
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: true
tasks:
# SET FACTS
- name: Get Domain from server name
set_fact:
ipaserver_domain: "{{ ansible_facts['fqdn'].split('.')[1:] |
join ('.') }}"
when: ipaserver_domain is not defined
# CLEANUP TEST ITEMS
- name: Ensure user testuser is absent
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testuser
state: absent
- name: Ensure host testhost is absent
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "{{ 'testhost.' + ipaserver_domain }}"
state: absent
# CREATE TEST ITEMS
- name: Ensure user testuser is present
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testuser
first: Test
last: User
register: result
failed_when: not result.changed or result.failed
- name: Ensure host testhost is present
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "{{ 'testhost.' + ipaserver_domain }}"
force: yes
reverse: no
register: result
failed_when: not result.changed or result.failed
# TESTS
- name: Ensure group membership has been rebuilt
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: group
state: rebuilt
register: result
failed_when: not result.changed or result.failed
- name: Ensure group membership has been rebuilt no_wait
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: group
no_wait: yes
state: rebuilt
register: result
failed_when: not result.changed or result.failed
- name: Ensure group membership for given users has been rebuilt
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
users:
- testuser
state: rebuilt
register: result
failed_when: not result.changed or result.failed
- name: Ensure hostgroup membership for given hosts has been rebuilt
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
hosts:
- "{{ 'testhost.' + ipaserver_domain }}"
state: rebuilt
register: result
failed_when: not result.changed or result.failed
- name: Ensure group membership for given users has been rebuilt with type group
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: group
users:
- testuser
state: rebuilt
register: result
failed_when: not result.changed or result.failed
- name: Ensure hostgroup membership for given hosts has been rebuilt with type hostgroup
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: hostgroup
hosts:
- "{{ 'testhost.' + ipaserver_domain }}"
state: rebuilt
register: result
failed_when: not result.changed or result.failed
- name: Ensure group membership rebuild fails with hosts
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: group
hosts:
- "{{ 'testhost.' + ipaserver_domain }}"
state: rebuilt
register: result
failed_when: not result.failed or
"hosts can not be set when type is 'group'" not in result.msg
- name: Ensure hostgroup membership rebuild fails with users
ipaautomember:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
automember_type: hostgroup
users:
- testuser
state: rebuilt
register: result
failed_when: not result.failed or
"users can not be set when type is 'hostgroup'" not in result.msg
# CLEANUP TEST ITEMS
- name: Ensure user testuser is absent
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testuser
state: absent
- name: Ensure host testhost is absent
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "{{ 'testhost.' + ipaserver_domain }}"
state: absent

View File

@@ -27,6 +27,12 @@ jobs:
container_name: centos-8
build_scenario_name: centos-8-build
- template: templates/build_container.yml
parameters:
job_name_suffix: Centos9
container_name: centos-9
build_scenario_name: centos-9-build
- template: templates/build_container.yml
parameters:
job_name_suffix: FedoraLatest

View File

@@ -0,0 +1,55 @@
plugins/module_utils/ansible_freeipa_module.py compile-2.6!skip
plugins/module_utils/ansible_freeipa_module.py import-2.6!skip
plugins/module_utils/ansible_freeipa_module.py pylint:ansible-bad-import-from
plugins/module_utils/ansible_freeipa_module.py pylint:ansible-bad-function
plugins/module_utils/ansible_freeipa_module.py pylint:ansible-format-automatic-specification
plugins/module_utils/ansible_freeipa_module.py pylint:ansible-deprecated-no-collection-name
plugins/modules/ipaclient_get_facts.py compile-2.6!skip
plugins/modules/ipaclient_get_facts.py import-2.6!skip
plugins/modules/ipaclient_api.py pylint:ansible-format-automatic-specification
plugins/modules/ipaclient_join.py pylint:ansible-format-automatic-specification
plugins/modules/ipaclient_test.py pylint:ansible-format-automatic-specification
plugins/modules/ipaconfig.py compile-2.6!skip
plugins/modules/ipaconfig.py import-2.6!skip
plugins/modules/ipadnsrecord.py compile-2.6!skip
plugins/modules/ipadnsrecord.py import-2.6!skip
plugins/modules/ipadnsrecord.py pylint:ansible-format-automatic-specification
plugins/modules/ipadnsrecord.py pylint:use-maxsplit-arg
plugins/modules/ipareplica_enable_ipa.py pylint:ansible-format-automatic-specification
plugins/modules/ipareplica_prepare.py pylint:ansible-format-automatic-specification
plugins/modules/ipareplica_test.py pylint:ansible-format-automatic-specification
plugins/modules/ipaserver_setup_ca.py compile-2.6!skip
plugins/modules/ipaserver_setup_ca.py import-2.6!skip
plugins/modules/ipaserver_test.py pylint:ansible-format-automatic-specification
plugins/modules/ipaservice.py compile-2.6!skip
plugins/modules/ipaservice.py import-2.6!skip
plugins/modules/ipavault.py compile-2.6!skip
plugins/modules/ipavault.py import-2.6!skip
roles/ipaclient/library/ipaclient_api.py pylint:ansible-format-automatic-specification
roles/ipaclient/library/ipaclient_join.py pylint:ansible-format-automatic-specification
roles/ipaclient/library/ipaclient_test.py pylint:ansible-format-automatic-specification
roles/ipareplica/library/ipareplica_enable_ipa.py pylint:ansible-format-automatic-specification
roles/ipareplica/library/ipareplica_prepare.py pylint:ansible-format-automatic-specification
roles/ipareplica/library/ipareplica_test.py pylint:ansible-format-automatic-specification
roles/ipaserver/library/ipaserver_test.py pylint:ansible-format-automatic-specification
roles/ipareplica/module_utils/ansible_ipa_replica.py pylint:ansible-format-automatic-specification
tests/external-signed-ca-with-automatic-copy/external-ca.sh shebang!skip
tests/pytests/conftest.py pylint:ansible-format-automatic-specification
tests/user/users.sh shebang!skip
tests/user/users_absent.sh shebang!skip
tests/utils.py pylint:ansible-format-automatic-specification
tests/utils.py pylint:subprocess-run-check
utils/ansible-doc-test shebang!skip
utils/ansible-ipa-client-install shebang!skip
utils/ansible-ipa-replica-install shebang!skip
utils/ansible-ipa-server-install shebang!skip
utils/build-galaxy-release.sh shebang!skip
utils/build-srpm.sh shebang!skip
utils/changelog shebang!skip
utils/galaxyfy-README.py shebang!skip
utils/galaxyfy-module-EXAMPLES.py shebang!skip
utils/galaxyfy-playbook.py shebang!skip
utils/galaxyfy.py shebang!skip
utils/gen_modules_docs.sh shebang!skip
utils/lint_check.sh shebang!skip
utils/new_module shebang!skip

View File

@@ -84,7 +84,7 @@ rm plugins/module_utils/ansible_ipa_*
rm plugins/modules/ipaserver_*
rm plugins/modules/ipareplica_*
rm plugins/modules/ipaclient_*
rm plugins/modules/ipabackup_*
rm plugins/action/ipaclient_*
rm plugins/action/ipabackup_*
rmdir plugins/action
git reset --hard

View File

@@ -26,7 +26,7 @@ param_docs = {
"sssd": "The installer sssd setting",
"dnsok": "The installer dnsok setting",
"dm_password": "Directory Manager password",
"dm_password": "Directory Manager password",
"password": "Admin user kerberos password",
"ip_addresses": "List of Master Server IP Addresses",
"domain": "Primary DNS domain of the IPA deployment",
@@ -55,10 +55,9 @@ param_docs = {
"no_pkinit": "Disable pkinit setup steps",
"no_ui_redirect": "Do not automatically redirect to the Web UI",
"external_ca": "External ca setting",
"setup_adtrust": "Configure AD trust capability",
"external_cert_files": [
"File containing the IPA CA certificate and the external CA certificate",
"chain"
"File containing the IPA CA certificate and the external CA "
"certificate chain"
],
"reverse_zones": "The reverse DNS zones to use",
"no_reverse": "Do not create new reverse DNS zone",
@@ -132,8 +131,8 @@ param_docs = {
"debug": "Turn on extra debugging",
"basedn": "The basedn of the IPA server (of the form dc=example,dc=com)",
"allow_repair": [
"Allow repair of already joined hosts. Contrary to ipaclient_force_join",
"the host entry will not be changed on the server"
"Allow repair of already joined hosts. Contrary to "
"ipaclient_force_join the host entry will not be changed on the server"
],
"backup": "File to backup",
"fqdn": [