Compare commits

...

101 Commits

Author SHA1 Message Date
Rafael Guterres Jeffman
a1230cabc6 Merge pull request #1242 from t-woerner/fix_build_galaxy_release_sh_offline2
utils/build-galaxy-release.sh: Fix unary operator expected (v2)
2024-05-27 11:00:03 -03:00
Thomas Woerner
411f5f3467 utils/build-galaxy-release.sh: Fix unary operator expected (v2)
This fixes a bad tests if offline is not set:
utils/build-galaxy-release.sh: line 130: [: -ne: unary operator expected

Fixes f17f83d6bd
2024-05-27 15:39:35 +02:00
Thomas Woerner
8779384614 Merge pull request #1225 from rjeffman/ci_pin_ansible_lint_version
Bump linter tools versions an fix linter errors
2024-05-27 14:33:52 +02:00
Rafael Guterres Jeffman
2cc1484ad7 Merge pull request #1229 from t-woerner/batch_command
Use batch command internally
2024-05-23 14:53:23 -03:00
Rafael Guterres Jeffman
77c1d206d3 fixup! pylint: Ignore usage of 'unicode' before assignment 2024-05-22 14:31:00 -03:00
Rafael Guterres Jeffman
52241fe233 pylint: ensure variables are initialized
pylint doesn't know that some functions may terminate execution, like,
AnsibleModule's fail_json, and assume that, depending on the code path,
some variables may not be initialized when used.

This change ensure that variables are always initialized independent of
the code path.
2024-05-22 10:50:34 -03:00
Rafael Guterres Jeffman
f53ca3ad39 pylint: Ignore usage of 'unicode' before assignment
New versions of pylint ignore Python 2 functions and types, evaluating
'unicode' as "undefined". ansible-freeipa will always define 'unicode'
when running under Python 3, and it is always defined under Python 2.

This patch fixes these false positives.
2024-05-22 10:42:00 -03:00
Rafael Guterres Jeffman
60905ef5bf upstream ci: Update Github actions
Github actions checkout v3.1.0 and setup-python v4.3.0 use deprecated
Node.js 16.

Bumping version to checkout v4.1.1 and setup-python v5.1.0 fixes the
workflows, as both use the recommended Node.js 20.

The checkout depth has been set to 1 (shallow copy) for all tasks that
do not require git history to be available.
2024-05-22 10:40:49 -03:00
Rafael Guterres Jeffman
0d48da060d lint tools: bump code verification tools versions
Bump version of ansible-lint, Flake8, Pylint and yamllint to newer
versions as used in Ansible tests.
2024-05-22 10:40:49 -03:00
Thomas Woerner
5cdbcf6442 ipahost: Enable batch command with keeponly
The use of the batch command is enabled for execute_ipa_commands.

Additionally keeponly is set to ["randompassword"] as this is the only
parameter that is used from the data returned from the commands.
2024-05-22 11:51:22 +02:00
Thomas Woerner
08b0fc02ba ipagroup: Enable batch command use with keeponly
The use of the batch command is enabled for execute_ipa_commands.

Additionally keeponly is set to [] as nothing is used from the data returned
from the commands.
2024-05-22 11:50:11 +02:00
Thomas Woerner
6cec03eb15 ipaservice: Enable batch command use with keeponly
The use of the batch command is enabled for execute_ipa_commands.

Additionally keeponly is set to [] as nothing is used from the data returned
from the commands.
2024-05-22 11:48:36 +02:00
Thomas Woerner
65a1fd7804 ipauser: Enable batch command use with keeponly
The use of the batch command is enabled for execute_ipa_commands.

Additionally keeponly is set to ["randompassword"] as this is the only
parameter that is used from the data returned from the commands.
2024-05-22 11:45:03 +02:00
Thomas Woerner
bcb6a68230 IPAAnsibleModule: Add support for batch command in execute_ipa_commands
The method execute_ipa_commands has been extended to handle multi
commands with the batch command.

New constants for execute_ipa_commands debugging:

    DEBUG_COMMAND_ALL = 0b1111
    DEBUG_COMMAND_LIST = 0b0001
        Print the while command list
    DEBUG_COMMAND_COUNT = 0b0010
        Print the command number
    DEBUG_COMMAND_BATCH = 0b0100
        Print information about the batch slice size and currently executed
        batch slice

New parameters have been added to execute_ipa_commands:

    batch: bool
        Enable batch command use to speed up processing
    batch_slice_size: integer
        Maximum mumber of commands processed in a slice with the batch
        command
    keeponly: list of string
        The attributes to keep in the results returned.
        Default: None (Keep all)
    debug: integer
        Enable debug output for the exection using DEBUG_COMMAND_*

Batch mode can be enabled within the module with setting batch to True
for execute_ipa_commands.

Fixes: #1128 (batch command support)
2024-05-22 11:44:38 +02:00
Rafael Guterres Jeffman
8f8a16f815 Merge pull request #1239 from t-woerner/fix_group_readme_rename
README-group.md: Add missing ":" in multi rename example
2024-05-21 09:09:11 -03:00
Rafael Guterres Jeffman
bfcc62a27f Merge pull request #1238 from t-woerner/fix_user_readme_rename
README-user.md: Fix state for user rename in example playbook
2024-05-21 09:08:25 -03:00
Rafael Guterres Jeffman
8ba32bfc26 Merge pull request #1237 from t-woerner/fix_idempotency_issues_ipahost
ipahost: Fix idempotency issues
2024-05-21 09:06:45 -03:00
Thomas Woerner
69306a6177 README-group.md: Add missing ":" in multi rename example
The name tag in the multi rename example is was missing a ":".
2024-05-16 21:21:39 +02:00
Thomas Woerner
967a2d8e56 README-user.md: Fix state for user rename in example playbook
A user rename requires "state: renamed". This has been wrong in the
example.
2024-05-16 21:02:05 +02:00
Rafael Guterres Jeffman
2626715db6 Merge pull request #1222 from t-woerner/ipaserver_use_IPAChangeConf_and_realm_to_ldapi_uri
ipaserver_prepare: Properly create IPA_DEFAULT_CONF
2024-05-16 12:16:08 -03:00
Rafael Guterres Jeffman
2166a9f7a2 Merge pull request #1231 from t-woerner/inventory_plugin
New inventory plugin
2024-05-14 09:00:37 -03:00
Rafael Guterres Jeffman
8b4bb631a5 Merge pull request #1235 from t-woerner/fix_idempotency_issues_ipauser
ipauser: Fix idempotency issues for members
2024-05-14 08:55:10 -03:00
Thomas Woerner
f17f83d6bd utils/build-galaxy-release.sh: Fix unary operator expected
This fixes a bad tests if offline is not set:
utils/build-galaxy-release.sh: line 130: [: -ne: unary operator expected
2024-05-14 12:59:14 +02:00
Thomas Woerner
a3517a3a23 New inventory plugin
The inventory plugin compiles a dynamic inventory from IPA domain, filters
servers by role(s).

Usage:

Create yml file, for example `freeipa.yml`:

    ---
    plugin: freeipa
    server: server.ipa.local
    ipaadmin_password: SomeADMINpassword
    verify: ca.crt

Get compiled inventory:

    ansible-inventory -i freeipa.yml --graph
2024-05-14 12:58:39 +02:00
Rafael Guterres Jeffman
5aa1c7cb57 Merge pull request #1236 from t-woerner/fix_idempotency_issues_ipaservice
ipaservice: Do not set continue to None for service_del
2024-05-13 12:06:25 -03:00
Thomas Woerner
15e9201dab ipahost: Fix idempotency issues
This simplified the result_handler and also made the exception_handler
superfluous.
2024-05-13 13:31:52 +02:00
Thomas Woerner
6caa58e8be ansible_freeipa_module: Import and provide normalize_sshpubkey
normalize_sshpubkey is imported from ipalib.util and also added to
__all__ for use in modules.
2024-05-13 13:31:47 +02:00
Thomas Woerner
5c61f14cc1 ipaservice: Do not set continue to None for service_del
delete_continue defaults to None. The use of continue: None is resulting
in an error with the batch command. Therefore only set continue if it is
not None.
2024-05-13 13:23:26 +02:00
Thomas Woerner
b3a74e616a ipauser: Fix idempotency issues for members
These are manager, principal, certificate and certmapdata.

The result_handler function has been adapted and the exception_handler
function has been removed.

A new function has been added:

   convert_certificate
2024-05-13 13:18:26 +02:00
Rafael Guterres Jeffman
cbff802d13 Merge pull request #1226 from t-woerner/ipalib.install.kinit_moved_to_ipalib_freeipa_7286
ipalib.install.kinit moved to ipalib
2024-03-28 10:57:02 -03:00
Rafael Guterres Jeffman
4ceb6aa05d Merge pull request #1224 from t-woerner/ipaclient_dns_resolver_fix_changed
ipaclient_configure_dns_resolver: Return proper changed state
2024-03-28 10:13:06 -03:00
Thomas Woerner
35614d7a88 ipalib.install.kinit moved to ipalib
FreeIPA PR https://github.com/freeipa/freeipa/pull/7286 moved
ipalib.install.kinit to ipalib.

It is first tried to import kinit_keytab and kinit_password from
ipalib.kinit, then ipalib.install.kinit and finally in some cases
where support for IPA 4.5.0 is needed still also ipapython.ipautil.

Related: https://github.com/freeipa/freeipa/pull/7286
2024-03-27 15:33:29 +01:00
Rafael Guterres Jeffman
7a9ea832a1 Merge pull request #1227 from t-woerner/fix_build_galaxy_release_sh_offline
utils/build-galaxy-release.sh: Fix offline default value
2024-03-26 12:17:08 -03:00
Thomas Woerner
2804ec3f83 utils/build-galaxy-release.sh: Fix offline default value
The offline default value was 0, which resulted in 0 for
${offline/1/--offline}.

This broke the ansible-galaxy collection install call.
2024-03-26 14:46:11 +01:00
Thomas Woerner
bef748cfdc ipaclient_configure_dns_resolver: Return proper changed state
The changed state returned from ipaclient_configure_dns_resolver was
always True. The internal functions (copies from FreeIPA code) have been
fixed to return a changed state.

Fixes: #1217 (ipaclient: Configure DNS resolver always reports as changed)
2024-03-21 16:19:46 +01:00
Rafael Guterres Jeffman
c24e8b498e Merge pull request #1223 from t-woerner/galaxy_collection_for_rpm
utils/build-galaxy-release.sh: Enable offline generation for rpm
2024-03-21 09:27:41 -03:00
Thomas Woerner
fe16df8a6c utils/build-galaxy-release.sh: Enable offline generation for rpm
Two new options have been added to enable the offline build within rpm:

    -o <A.B.C>  Build offline without using git, using version A.B.C
                Also enables -a
    -p <path>   Installation the generated collection in the path, the
                ansible_collections sub directory will be created and will
                contain the collection: ansible_collections/<namespace>/<name>
                Also enables -i

The usage text has been fixed also for specifying namespace and name.
The collection variable has been renamed to name.

Example usage:

    utils/build-galaxy-release.sh -o 1.12.1 \
        -p %{buildroot}%{_datadir}/ansible/collections \
        freeipa ansible_freeipa
2024-03-20 13:45:07 +01:00
Rafael Guterres Jeffman
d804dc470e Merge pull request #1221 from t-woerner/ipaserver_only_one_custodia_setup
ipaserver: Run custodia setup only once
2024-03-14 10:44:41 -03:00
Thomas Woerner
8fa3daece8 ipaserver_prepare: Properly create IPA_DEFAULT_CONF
Use IPAChangeConf and realm_to_ldapi_uri to create IPA_DEFAULT_CONF.

With realm_to_ldapi_uri the ldap_uri is correctly using /run instead of
/var/run.

Before IPA_DEFAULT_CONF was created using file operations.
2024-03-13 14:27:09 +01:00
Thomas Woerner
0cad1fa879 ipaserver: Run custodia setup only once
The custodia setup is executed twice. At first in
ipaserver_setup_custodia and then additionally in ipaserver_setup_ca.

The custodia setup code in ipaserver_setup_ca.py has been adapted to fit
the code in ipaserver_setup_custodia.py.

The extra Setup custodia step in the server roles has been removed
together with ipaserver_setup_custodia.py.
2024-03-13 13:15:24 +01:00
Rafael Guterres Jeffman
780e6b1436 Merge pull request #1220 from t-woerner/ipaserver_test_return_generated_domain_name
ipaserver_test: Return generated domain_name
2024-03-11 11:08:39 -03:00
Rafael Guterres Jeffman
216a5d4f9d Merge pull request #1215 from t-woerner/fix_ca_less_to_use_X.509_v3
Fix ca-less test to use X.509 v3 certificates
2024-03-11 11:04:08 -03:00
Thomas Woerner
f8ff833b03 ipaserver_test: Return generated domain_name
If ipaserver_domain is not given, the domain name is generated from the
host fqdn.

This generated value was so far not returned, but the empty given value
instead.
2024-03-11 14:02:51 +01:00
Thomas Woerner
b92da82661 Fix ca-less test to use X.509 v3 certificates
The generated certificates have been X.509 v1. This is not supported any
more. Only X.509 v3 is supported.

A new certificates/extensions.conf file has been added to make v3
certificates.

The existing certificates/pkinit/extensions.conf has been renamed to
certificates/pkinit-extensions.conf with additional changes. For example
"[kdc_cert]" had to be removed for v3.

The extensions config files are using environment variables, which are
set by the generate-certificates.sh script before calling openssl.

The script generate-certificates.sh has been reworked for a simpler
structure, also new options have been added: "ca" and "cleanup".
2024-03-05 11:17:17 +01:00
Thomas Woerner
ce05b5e137 Merge pull request #1213 from rjeffman/dnszone_fix_yaml_code_block
README-dnszone: Fix yaml code block declaration.
2024-02-27 13:10:19 +01:00
Rafael Guterres Jeffman
a826bf1781 README-dnszone: Fix yaml code block declaration.
There was a space between the code block marker and the highlight hint
in a playbook example.
2024-02-15 09:39:14 -03:00
Thomas Woerner
a3a6919416 Merge pull request #760 from rjeffman/ipadelegation_case_insensitive
ipadelegation: Fix idempotence issues due to capitalization.
2024-02-12 15:33:30 +01:00
Rafael Guterres Jeffman
e9c6e93608 ipadelegation: Fix idempotence issues due to capitalization.
This patch force processing of permission, attribute and group
attributes in lower case, to match behavior of IPA CLI, transforming
all of them into lowercase characters.

The new behavior fixes idempotence issues when mixing different
capitalization in different tasks for the same attribute.

A new test playbook is avaiable at:

    tests/delegation/test_delegation_member_case_insensitive.yml
2024-02-12 11:10:21 -03:00
Thomas Woerner
f40f4d4c9a Merge pull request #1201 from rjeffman/ipagroup_case_insensitive
ipagroup: Fix idempotence issues due to capitalization
2024-02-12 14:59:41 +01:00
Rafael Guterres Jeffman
7b7d9c9957 ipagroup: Fix idempotence issues due to capitalization
Some attributes for ipagroup objects are stored using lower case letters
and should be converted upon retrieving parameter data.

This patch adds the missing conversion and provides a new test playbook:

    tests/group/test_group_case_insensitive.yml
2024-02-12 09:11:12 -03:00
Rafael Guterres Jeffman
c0c3394d8d Merge pull request #1211 from t-woerner/disable_config_tests_for_pac_type_without_MS-PAC
Disable config tests for pac type without ms pac
2024-02-12 09:09:06 -03:00
Thomas Woerner
11205102af Merge pull request #1202 from rjeffman/ipahostgroup_idempotence_issues
ipahostgroup: Fix idempotence issues due to capitalization
2024-02-12 11:45:05 +01:00
Rafael Guterres Jeffman
22401d18d6 ipahostgroup: Fix idempotence issues due to capitalization
ipahostgroup parameters 'host', 'hostgroup', 'membermanager_user' and
'membermanager_group' must be compared in a case insensitive manner
and stored as lower case strings.

This patch fixes the comparison and storage of this parameters, and
change the handling of members to use the same structure as in newer
modules.

Two new tests files were added:

    tests/hostgroup/test_hostgroup_case_insensitive.yml
    tests/hostgroup/test_hostgroup_membermanager_case_insensitive.yml
2024-02-09 21:19:58 -03:00
Thomas Woerner
9b5a54c4fa Merge pull request #1203 from rjeffman/ipahbacrule_fix_idempotence_issues
ipahbacrule: Fix handling of hbacsvcgroup in members
2024-02-09 19:49:28 +01:00
Thomas Woerner
9920a76777 config: Disable config tests due to pac type requirement MS-PAC
The config tests are currently setting the pac type to empty or without
MS-PAC type. This results in failed authorization for IPA API.

An issue has been opened for FreeIPA to address this:
https://pagure.io/freeipa/issue/9527
2024-02-09 14:43:46 +01:00
Rafael Guterres Jeffman
249eab6047 Merge pull request #1208 from t-woerner/ipaclient_automount_with_new_install_states
ipaclient_setup_automount with new install states
2024-02-07 13:10:24 -03:00
Thomas Woerner
29f046b8e2 Merge pull request #1206 from rjeffman/ipaserver_fix_deploy_EL8
ipaserver: Fix deployment after Bronze-bit fix
2024-02-07 16:54:11 +01:00
Rafael Guterres Jeffman
2317c20556 ipaserver: Fix deployment after Bronze-bit fix
As FreeIPA now requires MS-PAC to be set in ipaKrbAuthzData to trigger
PAC generation, there's a timing issue that causes API malfunction which
is long enough to cause the client part insallation to fail.

By restarting KDC after DS password is set, we force cached values to be
refreshed, allowing the API to work correctly.

Resolves: https://github.com/freeipa/ansible-freeipa/issues/1200
2024-02-07 12:16:38 -03:00
Thomas Woerner
0d1f8b53b8 ipaclient_setup_automount: Only return changed if there was a change
The returned changed state was always True. changed is now only True if
automount_location is set and configure_automount was called.
2024-02-07 14:39:34 +01:00
Thomas Woerner
0a468d32e8 ipaclient_setup_automount with new install states
This is "Fix ipa-client-automount install/uninstall with new install
states" https://github.com/freeipa/freeipa/pull/7100 for ansible-freeipa:

Issue 8384 introduced a new installation state for the statestore
to identify when client/server installation is completely finished
rather than relying on has_files().

The problem is that ipa-client-automount may be called during
ipa-client-install and since installation is not complete at that
point the automount install was failing with "IPA client not
configured".

Add a new state, 'automount', to designate that automount installation
is in process. If check_client_configuration() fails it checks to
see if [installation] automount is True. If so it continues with the
installation.

This also addresses an issue where the filestore and statestore are
shared between the client and automount installers but the client
wasn't refreshing state after automount completed. This resulted in
an incomplete state and index file of backed-up files which caused
files to not be restored on uninstall and the state file to be
orphaned.

Fixes: https://pagure.io/freeipa/issue/9487
2024-02-07 14:39:04 +01:00
Rafael Guterres Jeffman
03c65bd761 Merge pull request #1207 from t-woerner/ipaclient_enable_SELinux_for_SSSD
ipaclient: Enable SELinux for SSSD
2024-02-07 00:38:13 -03:00
Rafael Guterres Jeffman
b87b346a0a ipahbacrule: Fix handling of hbacsvcgroup in members
FreeIPA provides a default hbacsvcgroup named "Sudo", with capital 'S',
that is different from every other hbacsvcgroup, which are all
represented by lower case letters.

As data from IPA API was not modified, this causes an idempotence error
when using different capitalization with the 'hbacsvcgroup' parameter.

This patch fixes the issue by using the CaseInsensitive comparator to
create the hbacsvcgroup list.

Tests were update to make sure a regression is not included in the
future.
2024-02-06 16:29:04 -03:00
Thomas Woerner
e92db5c5cd ipaclient: Enable SELinux for SSSD
This is "ipa-client-install: enable SELinux for SSSD"
https://github.com/freeipa/freeipa/pull/6978 for ansible-freeipa:

For passkeys (FIDO2) support, SSSD uses libfido2 library which needs
access to USB devices. Add SELinux booleans handling to ipa-client-install
so that correct SELinux booleans can be enabled and disabled during
install and uninstall. Ignore and record a warning when SELinux policy
does not support the boolean.

Fixes: https://pagure.io/freeipa/issue/9434
2024-02-06 14:39:19 +01:00
Thomas Woerner
1028f61b6c Merge pull request #899 from rjeffman/sudorule_add_runasuser_group
ipasudorule: Allow setting groups for runasuser.
2024-01-24 22:11:43 +01:00
Rafael Guterres Jeffman
1fde1764af ipasudorule: Allow setting groups for runasuser.
On IPA CLI sudorule-add/del-runasuser accept 'group' as a parameter,
and this option was missing in ansible-freeipa ipasudorule module.

This patch adds a new parameter 'runasuser_group' to allow setting
Groups of RunAs Users, as allowed by CLI and WebUI.

New example playboks can be found at:

    playbooks/sudorule/ensure-sudorule-runasusesr-group-is-absent.yml
    playbooks/sudorule/ensure-sudorule-runasusesr-group-is-present.yml
2024-01-23 12:04:02 -03:00
Thomas Woerner
4321478cf0 Merge pull request #1178 from rjeffman/ipagroup_rename
ipagroup: Add support for renaming groups
2023-12-21 20:47:47 +01:00
Rafael Guterres Jeffman
900c76e810 Merge pull request #1195 from t-woerner/Fixes_for_ansible_lint_6_22_1
Fixes for ansible-lint 6.22.1
2023-12-20 16:10:46 -03:00
Rafael Guterres Jeffman
1ecdbd3a49 ipagroup: Add support for renaming groups
FreeIPA suports renaming groupobjects with the CLI parameter "rename",
and this parameter was missing in ansible-freeipa ipagroup module.

This patch adds support for a new state 'renamed' and the 'rename'
parameter.

Tests were updated to cope with the changes.
2023-12-20 11:29:22 -03:00
Thomas Woerner
47a1d50c84 Fixes for ansible-lint 6.22.1
- Replace outdated noqa 503 with noqa no-handler
- Drop outdated and not needed noqa 505 for include_vars
- Drop outdated noqa deprecated-command-syntax for
  ansible.builtin.shell using cmd tag

These warnings have been reported by utils/lint_check.sh using
ansible-lint 6.22.1.
2023-12-20 14:38:24 +01:00
Rafael Guterres Jeffman
3fe41a5260 tests/group: Use module_defaults on tests_group
Use module_defaults to improve reading test cases.
2023-12-20 09:21:17 -03:00
Thomas Woerner
3a304e8bd7 Merge pull request #1174 from rjeffman/ipauser_rename
ipauser: Add support for renaming users
2023-12-20 11:40:50 +01:00
Thomas Woerner
86e089fd42 Merge pull request #1147 from rjeffman/dnszone_permission
ipadnszone: Add support for per-zone privilege delegation
2023-12-20 11:21:35 +01:00
Rafael Guterres Jeffman
3eb86b2c2d ipauser: Add support for renaming users
FreeIPA suports renaming user objects with the CLI parameter "rename",
and this parameter was missing in ansible-freeipa ipauser module.

This patch adds support for a new state 'renamed' and the 'rename'
parameter.

Tests were updated to cope with the changes.

Related to RHBZ#2234379, RHBZ#2234380

Fixes #1103
2023-12-19 11:44:31 -03:00
Rafael Guterres Jeffman
3bd68ac0fa ipadnszone: Add support for per-zone privilege delegation
IPA DNS Zones management can be delegated by adding a "Manage DNS zone"
permission. The CLI commands that manage these permissions are
dnszone-add-permission and dnszone-remove-permission.

The ansible-freeipa module ipadnszone did not have this capability, and
it now support dnszone per-zone management delegation by setting the
module parameter 'permission'. If set to 'true' the permission will be
assigned to the zone, if set to false the permission will be removed.
2023-12-19 11:28:46 -03:00
Rafael Guterres Jeffman
0f2c37612e Merge pull request #1169 from t-woerner/ipaclient_automount_location
ipaclient: Properly name automount_location var and add documentation
2023-12-19 09:46:41 -03:00
Thomas Woerner
4e831b0cb8 Merge pull request #1143 from rjeffman/global_handle_datatype
Handle data type or empty string in module_utils
2023-12-19 13:35:04 +01:00
Rafael Guterres Jeffman
34973c04c6 idoveridegroup: Use module.params_get_type
Use the commom parameter type handling method for parameters that accept
a value or an empty string.
2023-12-15 10:48:00 -03:00
Rafael Guterres Jeffman
bc694b722c idoverideuser: Use module.params_get_type
Use the commom parameter type handling method for parameters that accept
a value or an empty string.
2023-12-15 10:48:00 -03:00
Rafael Guterres Jeffman
92d579be41 ipapwpolicy: Use modules.params_get_type
Use the commom parameter type handling method for parameters that accept
a value or an empty string.
2023-12-15 10:48:00 -03:00
Rafael Guterres Jeffman
e55a41ca0c ansible_freeipa_module: Ensure data type when retrieving parameter
Some parameters, in modules, have a specific data type, but allow the
use of an empty string to clear the parameter.

By providing a method to retrieve the parameter with the correct data
type, or optionally an empty string, allows for consistency of parameter
handling between different modules.
2023-12-15 10:41:41 -03:00
Thomas Woerner
0f7ebd22fd Merge pull request #1149 from rjeffman/fix_rhel_4934
ipauser: Do not try to modify user when not changing password
2023-12-14 14:56:21 +01:00
Rafael Guterres Jeffman
f4c9e28715 Rename parameter 'allow_empty_string' to 'allow_empty_list_item'
The parameter 'allow_empty_string' in 'module_params_get' is used to
allow an item in a list to be an empty string. The problem is that the
naming is misleading, as it is checking a list item rather than a
string.

This patch rename the parameter to 'allow_empty_list_item' so that it
more clearly refers to list itens instead of standalone strings, and do
not collide with future parameters that may test for empty strings which
are not part of lists.
2023-12-08 14:12:52 -03:00
Thomas Woerner
81e6cbe6b7 Merge pull request #1187 from rjeffman/ipaclient_fix_otp_error_report
ipaclient: Fix OTP error reporting
2023-12-08 17:17:13 +01:00
Rafael Guterres Jeffman
9ecbe2315e Merge pull request #1189 from t-woerner/revert_temp_commit_de3c6c0
Revert "[TEMP] Enable only idp, service and user module tests"
2023-12-07 11:41:32 -03:00
Thomas Woerner
102d6c5a6d Revert "[TEMP] Enable only idp, service and user module tests"
This reverts commit de3c6c0ace.
2023-12-07 15:31:08 +01:00
Thomas Woerner
66bbc50c4d Merge pull request #1151 from rjeffman/ipareplica_support_cluster_ipaserver
ipareplica: Support inventory groups.ipaserver
2023-12-07 14:30:28 +01:00
Thomas Woerner
a38106afae Merge pull request #1184 from rjeffman/ci_inscrease_pr_test_timeout
upstream ci: Increase timeout for PR tests
2023-12-07 14:05:41 +01:00
Rafael Guterres Jeffman
47940b48c6 upstream ci: Increase timeout for PR tests
After the change for a single job to run PR tests, and if there is any
change to ansible_module_utils, all the playbook tests are executed,
and the result is a failure due to timeout.

This PR increases the timeout so that a PR with changes to
ansible_module_utils can have the tests executed.
2023-12-07 09:11:20 -03:00
Rafael Guterres Jeffman
8114120814 Merge pull request #1183 from t-woerner/idp_fix_validation_and_reset
ipaidp: Fix validation and reset of parameters
2023-12-07 09:10:26 -03:00
Thomas Woerner
505cb356c1 Merge pull request #1188 from rjeffman/ci_bump_ansible_lint
Bump minimum ansible-lint version to 6.22
2023-12-07 10:11:21 +01:00
Rafael Guterres Jeffman
d2e0cad90b Bump minimum ansible-lint version to 6.22
By the first quarter of 2024, all collections must pass ansible-lint
tests run with version 6.22.x. This PR ensure that all ansible-freeipa
tests depending on ansible-lint use a valid version of it.
2023-12-06 15:35:17 -03:00
Rafael Guterres Jeffman
9c735939a2 Merge pull request #1186 from t-woerner/test_host_random_conditional_statements_no_jinja2_templating
test_host_random: No jinja2 templating in conditional statements
2023-12-06 15:26:38 -03:00
Rafael Guterres Jeffman
22214dafff ipaclient: Fix OTP error reporting
When deploying an IPA client with ipaclient, if an error occured while
getting an OTP, no error message is logged, as the task that logs the
error is not excuted due to the previous taks failure.

By adding a 'rescue' section to the code block and moving the error
reporting to this new section, we ensure that the proper error messages
will be reported.
2023-12-06 14:26:00 -03:00
Thomas Woerner
2c9ee7d842 test_host_random: No jinja2 templating in conditional statements
With ansible-core 2.14.12 using jinja2 templating in conditional
statements with ansible_facts['fqdn'] is marked as unsafe and results in
a failure.

The issues with using jinja2 templating in conditional statements for
asserts have been solved and a new server_fqdn fact has been added for
ansible_facts['fqdn'].
2023-12-06 13:23:47 +01:00
Thomas Woerner
de3c6c0ace [TEMP] Enable only idp, service and user module tests 2023-12-05 16:44:55 +01:00
Thomas Woerner
ff084fbd96 ipaidp: Fix validation and reset of parameters
The uri parameters auth_uri, dev_auth_uri, token_uri, userinfo_uri and
keys_uri have not been validated before. Also the base_url was not
normalized. The auth_uri, dev_auth_uri, token_uri and userinfo_uri need
to be set for new entries, but might be empty or empty string for reset
or updates.

The ipaidpclientsecret needs to be decoded from binary string in
find_idp result to not trigger no change ipd_mod calls.

The code for validate_uri and base_url normalization has been copied
from the ipaserver idp plugin.

ansible_freeipa_module:
urlparse from urllib.parse with a fallback to six.moves.urllib.parse is
imported and also exported. urlparse is needed for validate_uri in ipaidp
module.

Resolves: RHEL-17954, RHEL-17955, RHEL-17957 and RHEL-17958
2023-12-05 16:30:50 +01:00
Rafael Guterres Jeffman
ca5496918a ipauser: Do not try to modify user when not changing password
If a playbook to ensure the existence of a user contains 'random:false'
and 'update_password: always' is executed twice, the second execution
will raise an exception due to "No modifications to perform", as there
is actually nothing to modify.

The fix for the issue is to remove 'random' if it is not set to true, as
setting it to 'false' would have no effect on the user object.

Related: https://issues.redhat.com/browse/RHEL-4934
2023-11-28 16:03:27 -03:00
Rafael Guterres Jeffman
48c0fd0a28 Merge pull request #1180 from t-woerner/freeipa_9297_pwpolicy_minlength_reset
test_pwpolicy: minlength parameter can be reset with empty string now
2023-11-28 08:59:50 -03:00
Thomas Woerner
f2a1d50b82 test_pwpolicy: minlength parameter can be reset with empty string now
The reset of the minlength parameter failed with an internal error so
far. This has been fixed in IPA and therefore requires to fix the test
in ansible-freeipa also.

Related: https://pagure.io/freeipa/issue/9297
2023-11-28 11:48:31 +01:00
Thomas Woerner
b22bf4dfb9 ipaclient: Properly name automount_location var and add documentation
The ipaclient_automount_location variable was badly named as
ipaautomount_location. Additionally it was not documented in the role
README file.

Fixes: #1166 (.. automount-location to the ipa-client role)
2023-11-08 12:33:41 +01:00
Rafael Guterres Jeffman
f012da22ce ipareplica: Support inventory groups.ipaserver
Altough most of ansible-freeipa documentation and playbooks use
'ipaserver' as the group for the first server deployed for a realm, the
ipareplica role only supported the use of groups["ipaservers"] as an
alternative to set ipareplica_servers.

Also supporting groups.ipaserver, as already supported by the ipaclient
role, make ansible-freeipa playbooks more consistent and current
documentation and examples easier to follow when deploying a cluster
with a server and a replica.
2023-10-20 13:43:06 -03:00
94 changed files with 3174 additions and 1119 deletions

View File

@@ -8,7 +8,7 @@ jobs:
name: Verify ansible-test sanity
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- name: Run ansible-test

View File

@@ -8,10 +8,10 @@ jobs:
name: Check Ansible Documentation with ansible-core 2.13.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
fetch-depth: 1
- uses: actions/setup-python@v5.1.0
with:
python-version: '3.x'
- name: Install Ansible 2.13
@@ -25,10 +25,10 @@ jobs:
name: Check Ansible Documentation with ansible-core 2.14.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
fetch-depth: 1
- uses: actions/setup-python@v5.1.0
with:
python-version: '3.x'
- name: Install Ansible 2.14
@@ -42,10 +42,10 @@ jobs:
name: Check Ansible Documentation with ansible-core 2.15.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
fetch-depth: 1
- uses: actions/setup-python@v5.1.0
with:
python-version: '3.x'
- name: Install Ansible 2.15
@@ -59,10 +59,10 @@ jobs:
name: Check Ansible Documentation with latest Ansible version.
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
fetch-depth: 1
- uses: actions/setup-python@v5.1.0
with:
python-version: '3.x'
- name: Install Ansible-latest

View File

@@ -8,15 +8,15 @@ jobs:
name: Verify ansible-lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
- uses: actions/setup-python@v5.1.0
with:
python-version: "3.x"
- name: Run ansible-lint
run: |
pip install "ansible-core>=2.16,<2.17" 'ansible-lint>=6.21'
pip install "ansible-core>=2.16,<2.17" 'ansible-lint==6.22'
utils/build-galaxy-release.sh -ki
cd .galaxy-build
ansible-lint --profile production --exclude tests/integration/ --exclude tests/unit/ --parseable --nocolor
@@ -25,10 +25,10 @@ jobs:
name: Verify yamllint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
fetch-depth: 1
- uses: actions/setup-python@v5.1.0
with:
python-version: "3.x"
- name: Run yaml-lint
@@ -38,10 +38,10 @@ jobs:
name: Verify pydocstyle
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
fetch-depth: 1
- uses: actions/setup-python@v5.1.0
with:
python-version: "3.x"
- name: Run pydocstyle
@@ -53,10 +53,10 @@ jobs:
name: Verify flake8
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
fetch-depth: 1
- uses: actions/setup-python@v5.1.0
with:
python-version: "3.x"
- name: Run flake8
@@ -68,10 +68,10 @@ jobs:
name: Verify pylint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
- uses: actions/setup-python@v4.3.0
fetch-depth: 1
- uses: actions/setup-python@v5.1.0
with:
python-version: "3.x"
- name: Run pylint
@@ -83,8 +83,8 @@ jobs:
name: Shellcheck
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
fetch-depth: 1
- name: Run ShellCheck
uses: ludeeus/action-shellcheck@master

View File

@@ -8,9 +8,9 @@ jobs:
name: Verify readme
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3.1.0
- uses: actions/checkout@v4.1.1
with:
fetch-depth: 0
fetch-depth: 1
- name: Run readme test
run: |
error=0

View File

@@ -1,7 +1,7 @@
---
repos:
- repo: https://github.com/ansible/ansible-lint.git
rev: v6.22.0
rev: v24.5.0
hooks:
- id: ansible-lint
always_run: false
@@ -21,20 +21,20 @@ repos:
--parseable
--nocolor
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.32.0
rev: v1.35.1
hooks:
- id: yamllint
files: \.(yaml|yml)$
- repo: https://github.com/pycqa/flake8
rev: 6.0.0
rev: 7.0.0
hooks:
- id: flake8
- repo: https://github.com/pycqa/pydocstyle
rev: 6.0.0
rev: 6.3.0
hooks:
- id: pydocstyle
- repo: https://github.com/pycqa/pylint
rev: v3.0.2
rev: v3.2.2
hooks:
- id: pylint
args:

View File

@@ -133,6 +133,22 @@ Example playbook to enable a zone:
state: enabled
```
Example playbook to allow per-zone privilege delegation:
```yaml
---
- name: Playbook to enable per-zone privilege delegation
hosts: ipaserver
become: true
tasks:
- name: Enable privilege delegation.
ipadnszone:
ipaadmin_password: SomeADMINpassword
name: testzone.local
permission: true
```
Example playbook to remove a zone:
```yaml
@@ -223,6 +239,7 @@ Variable | Description | Required
`ttl`| Time to live for records at zone apex | no
`default_ttl`| Time to live for records without explicit TTL definition | no
`nsec3param_rec`| NSEC3PARAM record for zone in format: hash_algorithm flags iterations salt | no
`permission` \| `managedby` | Set per-zone access delegation permission. | no
`skip_overlap_check`| Force DNS zone creation even if it will overlap with an existing zone | no
`skip_nameserver_check` | Force DNS zone creation even if nameserver is not resolvable | no
@@ -238,4 +255,6 @@ Variable | Description | Returned When
Authors
=======
Sergio Oliveira Campos
- Sergio Oliveira Campos
- Thomas Woerner
- Rafael Jeffman

View File

@@ -130,6 +130,45 @@ And ensure the presence of the groups with this example playbook:
groups: "{{ groups }}"
```
Example playbook to rename a group:
```yaml
---
- name: Playbook to rename a single group
hosts: ipaserver
become: false
gather_facts: false
tasks:
- name: Rename group appops to webops
ipagroup:
ipaadmin_password: SomeADMINpassword
name: appops
rename: webops
state: renamed
```
Several groups can also be renamed with a single task, as in the example playbook:
```yaml
---
- name: Playbook to rename multiple groups
hosts: ipaserver
become: false
gather_facts: false
tasks:
- name: Rename group1 to newgroup1 and group2 to newgroup2
ipagroup:
ipaadmin_password: SomeADMINpassword
groups:
- name: group1
rename: newgroup1
- name: group2
rename: newgroup2
state: renamed
```
Example playbook to add users to a group:
```yaml
@@ -262,11 +301,13 @@ Variable | Description | Required
`membermanager_group` | List of member manager groups assigned to this group. Only usable with IPA versions 4.8.4 and up. | no
`externalmember` \| `ipaexternalmember` \| `external_member`| List of members of a trusted domain in DOM\\name or name@domain form. | no
`idoverrideuser` | List of user ID overrides to manage. Only usable with IPA versions 4.8.7 and up.| no
`rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
`action` | Work on group or member level. It can be on of `member` or `group` and defaults to `group`. | no
`state` | The state to ensure. It can be one of `present` or `absent`, default: `present`. | yes
`state` | The state to ensure. It can be one of `present`, `absent` or `renamed`, default: `present`. | yes
Authors
=======
Thomas Woerner
- Thomas Woerner
- Rafael Jeffman

View File

@@ -0,0 +1,106 @@
Inventory plugin
================
Description
-----------
The inventory plugin compiles a dynamic inventory from IPA domain. The servers can be filtered by their role(s).
This plugin is using the Python requests binding, that is only available for Python 3.7 and up.
Features
--------
* Dynamic inventory
Supported FreeIPA Versions
--------------------------
FreeIPA versions 4.6.0 and up are supported by the inventory plugin.
Requirements
------------
**Controller**
* Ansible version: 2.13+
**Node**
* Supported FreeIPA version (see above)
Configuration
=============
The inventory plugin is automatically enabled from the Ansible collection or from the top directory of the git repo if the `plugins` folder is linked to `~/.ansible`.
If `ansible.cfg` was modified to point to the roles and modules with `roles_path`, `library` and `module_utils` tag, then it is needed to set `inventory_plugins` also:
```
inventory_plugins = /my/dir/ansible-freeipa/plugins/inventory
```
Usage
=====
Example inventory file "freeipa.yml":
```yml
---
plugin: freeipa
server: server.ipa.local
ipaadmin_password: SomeADMINpassword
```
Example inventory file "freeipa.yml" with server TLS certificate verification using local copy of `/etc/ipa/ca.crt` from the server:
```yml
---
plugin: freeipa
server: server.ipa.local
ipaadmin_password: SomeADMINpassword
verify: ca.crt
```
How to use the plugin
---------------------
With the `ansible-inventory` command it is possible to show the generated inventorey:
```bash
ansible-inventory -v -i freeipa.yml --graph
```
Example inventory file "freeipa.yml" for use with `playbooks/config/retrieve-config.yml`:
```yml
---
plugin: freeipa
server: server.ipa.local
ipaadmin_password: SomeADMINpassword
inventory_group: ipaserver
```
```bash
ansible-playbook -u root -i ipa.yml playbooks/config/retrieve-config.yml
```
Variables
=========
Variable | Description | Required
-------- | ----------- | --------
`ipaadmin_principal` | The admin principal is a string and defaults to `admin` | no
`ipaadmin_password` | The admin password is a string and is required if there is no admin ticket available on the node | no
`server` | The FQDN of server to start the scan. (string) | yes
`verify` | The server TLS certificate file for verification (/etc/ipa/ca.crt). Turned off if not set. (string) | yes
`role` | The role(s) of the server. If several roles are given, only servers that have all the roles are returned. (list of strings) (choices: "IPA master", "CA server", "KRA server", "DNS server", "AD trust controller", "AD trust agent") | no
`inventory_group` | The inventory group to create. The default group name is "ipaservers". | no
Authors
=======
- Thomas Woerner

View File

@@ -93,6 +93,26 @@ Example playbook to make sure sudocmds are not present in Sudo Rule:
state: absent
```
Example playbook to ensure a Group of RunAs User is present in sudo rule:
```yaml
---
- name: Playbook to manage sudorule member
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member
```
Example playbook to make sure Sudo Rule is absent:
```yaml

View File

@@ -279,7 +279,6 @@ Example playbook to disable a user:
This can also be done as an alternative with the `users` variable containing only names.
Example playbook to enable users:
```yaml
@@ -298,6 +297,22 @@ Example playbook to enable users:
This can also be done as an alternative with the `users` variable containing only names.
Example playbook to rename users:
```yaml
---
- name: Playbook to handle users
hosts: ipaserver
become: true
tasks:
# Rename user pinky to reddy
- ipauser:
ipaadmin_password: SomeADMINpassword
name: pinky
rename: reddy
state: renamed
```
Example playbook to unlock users:
@@ -401,7 +416,7 @@ Variable | Description | Required
`update_password` | Set password for a user in present state only on creation or always. It can be one of `always` or `on_create` and defaults to `always`. | no
`preserve` | Delete a user, keeping the entry available for future use. (bool) | no
`action` | Work on user or member level. It can be on of `member` or `user` and defaults to `user`. | no
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes
`state` | The state to ensure. It can be one of `present`, `absent`, `enabled`, `disabled`, `renamed`, `unlocked` or `undeleted`, default: `present`. Only `names` or `users` with only `name` set are allowed if state is not `present`. | yes
@@ -458,10 +473,10 @@ Variable | Description | Required
`smb_profile_path:` \| `ipantprofilepath` | SMB profile path, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_dir` \| `ipanthomedirectory` | SMB Home Directory, in UNC format. Requires FreeIPA version 4.8.0+. | no
`smb_home_drive` \| `ipanthomedirectorydrive` | SMB Home Directory Drive, a single upercase letter (A-Z) followed by a colon (:), for example "U:". Requires FreeIPA version 4.8.0+. | no
`rename` \| `new_name` | Rename the user object to the new name string. Only usable with `state: renamed`. | no
`nomembers` | Suppress processing of membership attributes. (bool) | no
Return Values
=============
@@ -477,5 +492,5 @@ Variable | Description | Returned When
Authors
=======
Thomas Woerner
Rafael Jeffman
- Thomas Woerner
- Rafael Jeffman

View File

@@ -13,6 +13,7 @@ Features
* Repair mode for clients
* Backup and restore, also to and from controller
* Smartcard setup for servers and clients
* Inventory plugin freeipa
* Modules for automembership rule management
* Modules for automount key management
* Modules for automount location management
@@ -108,9 +109,10 @@ You can use the roles directly within the top directory of the git repo, but to
You can either adapt ansible.cfg:
```
roles_path = /my/dir/ansible-freeipa/roles
library = /my/dir/ansible-freeipa/plugins/modules
module_utils = /my/dir/ansible-freeipa/plugins/module_utils
roles_path = /my/dir/ansible-freeipa/roles
library = /my/dir/ansible-freeipa/plugins/modules
module_utils = /my/dir/ansible-freeipa/plugins/module_utils
inventory_plugins = /my/dir/ansible-freeipa/plugins/inventory
```
Or you can link the directories:
@@ -470,3 +472,8 @@ Modules in plugin/modules
* [ipavault](README-vault.md)
If you want to write a new module please read [writing a new module](plugins/modules/README.md).
Inventory plugins in plugin/inventory
=====================================
* [freeipa](README-inventory-plugin-freeipa.md)

View File

@@ -1,3 +1,4 @@
---
- name: Certificate manage example
hosts: ipaserver
become: false

View File

@@ -0,0 +1,14 @@
---
- name: Playbook to manage sudorule member
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Ensure sudorule 'runasuser' do not have 'ipasuers' group as runas users.
ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member
state: absent

View File

@@ -0,0 +1,13 @@
---
- name: Playbook to manage sudorule member
hosts: ipaserver
become: no
gather_facts: no
tasks:
- name: Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member

View File

@@ -0,0 +1,180 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Copyright (C) 2024 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
"metadata_version": "1.0",
"supported_by": "community",
"status": ["preview"],
}
DOCUMENTATION = """
---
name: freeipa
plugin_type: inventory
version_added: "1.13"
short_description: Compiles a dynamic inventory from IPA domain
description: |
Compiles a dynamic inventory from IPA domain, filters servers by role(s).
options:
plugin:
description: Marks this as an instance of the "freeipa" plugin.
required: True
choices: ["freeipa"]
ipaadmin_principal:
description: The admin principal.
default: admin
type: str
ipaadmin_password:
description: The admin password.
required: true
type: str
server:
description: FQDN of server to start the scan.
type: str
required: true
verify:
description: |
The server TLS certificate file for verification (/etc/ipa/ca.crt).
Turned off if not set.
type: str
required: false
role:
description: |
The role(s) of the server. If several roles are given, only servers
that have all the roles are returned.
type: list
elements: str
choices: ["IPA master", "CA server", "KRA server", "DNS server",
"AD trust controller", "AD trust agent"]
required: false
inventory_group:
description: |
The inventory group to create. The default group name is "ipaservers".
type: str
default: ipaservers
author:
- Thomas Woerner (@t-woerner)
"""
EXAMPLES = """
# inventory.config file in YAML format
plugin: freeipa
server: ipaserver-01.ipa.local
ipaadmin_password: SomeADMINpassword
# inventory.config file in YAML format with server TLS certificate verification
plugin: freeipa
server: ipaserver-01.ipa.local
ipaadmin_password: SomeADMINpassword
verify: ca.crt
"""
import os
import requests
try:
from requests.packages import urllib3
except ImportError:
import urllib3
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
from ansible import constants
from ansible.errors import AnsibleParserError
from ansible.module_utils.common.text.converters import to_native
from ansible.plugins.inventory import BaseInventoryPlugin
from ansible.module_utils.six.moves.urllib.parse import quote
class InventoryModule(BaseInventoryPlugin):
NAME = 'freeipa'
def verify_file(self, path):
# pylint: disable=super-with-arguments
if super(InventoryModule, self).verify_file(path):
_name, ext = os.path.splitext(path)
if ext in constants.YAML_FILENAME_EXTENSIONS:
return True
return False
def parse(self, inventory, loader, path, cache=False):
# pylint: disable=super-with-arguments
super(InventoryModule, self).parse(inventory, loader, path,
cache=cache)
self._read_config_data(path) # This also loads the cache
self.get_option("plugin")
ipaadmin_principal = self.get_option("ipaadmin_principal")
ipaadmin_password = self.get_option("ipaadmin_password")
server = self.get_option("server")
verify = self.get_option("verify")
role = self.get_option("role")
inventory_group = self.get_option("inventory_group")
if verify is not None:
if not os.path.exists(verify):
raise AnsibleParserError("ERROR: Could not load %s" % verify)
else:
verify = False
self.inventory.add_group(inventory_group)
ipa_url = "https://%s/ipa" % server
s = requests.Session()
s.headers.update({"referer": ipa_url})
s.headers.update({"Content-Type":
"application/x-www-form-urlencoded"})
s.headers.update({"Accept": "text/plain"})
data = 'user=%s&password=%s' % (quote(ipaadmin_principal, safe=''),
quote(ipaadmin_password, safe=''))
response = s.post("%s/session/login_password" % ipa_url,
data=data, verify=verify)
# Now use json API
s.headers.update({"Content-Type": "application/json"})
kw_args = {}
if role is not None:
kw_args["servrole"] = role
json_data = {
"method" : "server_find",
"params": [[], kw_args],
"id": 0
}
response = s.post("%s/session/json" % ipa_url, json=json_data,
verify=verify)
json_res = response.json()
error = json_res.get("error")
if error is not None:
raise AnsibleParserError("ERROR: %s" % to_native(error))
if "result" in json_res and "result" in json_res["result"]:
res = json_res["result"].get("result")
if isinstance(res, list):
for server in res:
self.inventory.add_host(server["cn"][0],
group=inventory_group)

View File

@@ -25,12 +25,24 @@ from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
__all__ = ["gssapi", "netaddr", "api", "ipalib_errors", "Env",
__all__ = ["DEBUG_COMMAND_ALL", "DEBUG_COMMAND_LIST",
"DEBUG_COMMAND_COUNT", "DEBUG_COMMAND_BATCH",
"gssapi", "netaddr", "api", "ipalib_errors", "Env",
"DEFAULT_CONFIG", "LDAP_GENERALIZED_TIME_FORMAT",
"kinit_password", "kinit_keytab", "run", "DN", "VERSION",
"paths", "tasks", "get_credentials_if_valid", "Encoding",
"DNSName", "getargspec", "certificate_loader",
"write_certificate_list", "boolean", "template_str"]
"write_certificate_list", "boolean", "template_str",
"urlparse", "normalize_sshpubkey"]
DEBUG_COMMAND_ALL = 0b1111
# Print the while command list:
DEBUG_COMMAND_LIST = 0b0001
# Print the number of commands:
DEBUG_COMMAND_COUNT = 0b0010
# Print information about the batch slice size and currently executed batch
# slice:
DEBUG_COMMAND_BATCH = 0b0100
import os
# ansible-freeipa requires locale to be C, IPA requires utf-8.
@@ -43,6 +55,7 @@ import shutil
import socket
import base64
import ast
import time
from datetime import datetime
from contextlib import contextmanager
from ansible.module_utils.basic import AnsibleModule
@@ -86,9 +99,13 @@ try:
from ipalib.constants import DEFAULT_CONFIG, LDAP_GENERALIZED_TIME_FORMAT
try:
from ipalib.install.kinit import kinit_password, kinit_keytab
from ipalib.kinit import kinit_password, kinit_keytab
except ImportError:
from ipapython.ipautil import kinit_password, kinit_keytab
try:
from ipalib.install.kinit import kinit_password, kinit_keytab
except ImportError:
# pre 4.5.0
from ipapython.ipautil import kinit_password, kinit_keytab
from ipapython.ipautil import run
from ipapython.ipautil import template_str
from ipapython.dn import DN
@@ -147,6 +164,13 @@ try:
except ImportError:
_dcerpc_bindings_installed = False # pylint: disable=invalid-name
try:
from urllib.parse import urlparse
except ImportError:
from ansible.module_utils.six.moves.urllib.parse import urlparse
from ipalib.util import normalize_sshpubkey
except ImportError as _err:
ANSIBLE_FREEIPA_MODULE_IMPORT_ERROR = str(_err)
@@ -464,20 +488,22 @@ def _afm_convert(value):
return value
def module_params_get(module, name, allow_empty_string=False):
def module_params_get(module, name, allow_empty_list_item=False):
value = _afm_convert(module.params.get(name))
# Fail on empty strings in the list or if allow_empty_string is True
# if there is another entry in the list together with the empty
# string.
# Fail on empty strings in the list or if allow_empty_list_item is True
# if there is another entry in the list together with the empty string.
# Due to an issue in Ansible it is possible to use the empty string
# "" for lists with choices, even if the empty list is not part of
# the choices.
# Ansible issue https://github.com/ansible/ansible/issues/77108
if isinstance(value, list):
for val in value:
if isinstance(val, (str, unicode)) and not val:
if not allow_empty_string:
if (
isinstance(val, (str, unicode)) # pylint: disable=W0012,E0606
and not val
):
if not allow_empty_list_item:
module.fail_json(
msg="Parameter '%s' contains an empty string" %
name)
@@ -489,8 +515,8 @@ def module_params_get(module, name, allow_empty_string=False):
return value
def module_params_get_lowercase(module, name, allow_empty_string=False):
value = module_params_get(module, name, allow_empty_string)
def module_params_get_lowercase(module, name, allow_empty_list_item=False):
value = module_params_get(module, name, allow_empty_list_item)
if isinstance(value, list):
value = [v.lower() for v in value]
if isinstance(value, (str, unicode)):
@@ -498,6 +524,48 @@ def module_params_get_lowercase(module, name, allow_empty_string=False):
return value
def module_params_get_with_type_cast(
module, name, datatype, allow_empty=False
):
"""
Retrieve value set for module parameter as a specific data type.
Parameters
----------
module: AnsibleModule
The module from where to get the parameter value from.
name: string
The name of the parameter to retrieve.
datatype: type
The type to convert the value to, if value is not empty.
allow_empty: bool
Allow an empty string for non list parameters or a list
containing (only) an empty string item. This is used for
resetting parameters to the default value.
"""
value = module_params_get(module, name, allow_empty)
if not allow_empty and value == "":
module.fail_json(
msg="Argument '%s' must not be an empty string" % (name,)
)
if value is not None and value != "":
try:
if datatype is bool:
# We let Ansible handle bool values
value = boolean(value)
else:
value = datatype(value)
except ValueError:
module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, name)
)
except TypeError as terr:
# If Ansible fails to parse a boolean, it will raise TypeError
module.fail_json(msg="Param '%s': %s" % (name, str(terr)))
return value
def api_get_domain():
return api.env.domain
@@ -1045,7 +1113,7 @@ class IPAAnsibleModule(AnsibleModule):
finally:
temp_kdestroy(ccache_dir, ccache_name)
def params_get(self, name, allow_empty_string=False):
def params_get(self, name, allow_empty_list_item=False):
"""
Retrieve value set for module parameter.
@@ -1053,13 +1121,13 @@ class IPAAnsibleModule(AnsibleModule):
----------
name: string
The name of the parameter to retrieve.
allow_empty_string: bool
allow_empty_list_item: bool
The parameter allowes to have empty strings in a list
"""
return module_params_get(self, name, allow_empty_string)
return module_params_get(self, name, allow_empty_list_item)
def params_get_lowercase(self, name, allow_empty_string=False):
def params_get_lowercase(self, name, allow_empty_list_item=False):
"""
Retrieve value set for module parameter as lowercase, if not None.
@@ -1067,11 +1135,34 @@ class IPAAnsibleModule(AnsibleModule):
----------
name: string
The name of the parameter to retrieve.
allow_empty_string: bool
allow_empty_list_item: bool
The parameter allowes to have empty strings in a list
"""
return module_params_get_lowercase(self, name, allow_empty_string)
return module_params_get_lowercase(self, name, allow_empty_list_item)
def params_get_with_type_cast(
self, name, datatype, allow_empty=True
):
"""
Retrieve value set for module parameter as a specific data type.
Parameters
----------
name: string
The name of the parameter to retrieve.
datatype: type
The type to convert the value to, if not empty.
datatype: type
The type to convert the value to, if value is not empty.
allow_empty: bool
Allow an empty string for non list parameters or a list
containing (only) an empty string item. This is used for
resetting parameters to the default value.
"""
return module_params_get_with_type_cast(
self, name, datatype, allow_empty)
def params_fail_used_invalid(self, invalid_params, state, action=None):
"""
@@ -1239,7 +1330,8 @@ class IPAAnsibleModule(AnsibleModule):
def execute_ipa_commands(self, commands, result_handler=None,
exception_handler=None,
fail_on_member_errors=False,
**handlers_user_args):
batch=False, batch_slice_size=100, debug=False,
keeponly=None, **handlers_user_args):
"""
Execute IPA API commands from command list.
@@ -1256,6 +1348,16 @@ class IPAAnsibleModule(AnsibleModule):
Returns True to continue to next command, else False
fail_on_member_errors: bool
Use default member error handler handler member_error_handler
batch: bool
Enable batch command use to speed up processing
batch_slice_size: integer
Maximum mumber of commands processed in a slice with the batch
command
keeponly: list of string
The attributes to keep in the results returned from the commands
Default: None (Keep all)
debug: integer
Enable debug output for the exection using DEBUG_COMMAND_*
handlers_user_args: dict (user args mapping)
The user args to pass to result_handler and exception_handler
functions
@@ -1325,34 +1427,125 @@ class IPAAnsibleModule(AnsibleModule):
if "errors" in argspec.args:
handlers_user_args["errors"] = _errors
if debug & DEBUG_COMMAND_LIST:
self.tm_warn("commands: %s" % repr(commands))
if debug & DEBUG_COMMAND_COUNT:
self.tm_warn("#commands: %s" % len(commands))
# Turn off batch use for server context when it lacks the keeponly
# option as it lacks https://github.com/freeipa/freeipa/pull/7335
# This is an important fix about reporting errors in the batch
# (example: "no modifications to be performed") that results in
# aborted processing of the batch and an error about missing
# attribute principal. FreeIPA issue #9583
batch_has_keeponly = "keeponly" in api.Command.batch.options
if batch and api.env.in_server and not batch_has_keeponly:
self.debug(
"Turning off batch processing for batch missing keeponly")
batch = False
changed = False
for name, command, args in commands:
try:
if name is None:
result = self.ipa_command_no_name(command, args)
else:
result = self.ipa_command(command, name, args)
if batch:
# batch processing
batch_args = []
for ci, (name, command, args) in enumerate(commands):
if len(batch_args) < batch_slice_size:
batch_args.append({
"method": command,
"params": ([name], args)
})
if "completed" in result:
if result["completed"] > 0:
changed = True
else:
changed = True
# If result_handler is not None, call it with user args
# defined in **handlers_user_args
if result_handler is not None:
result_handler(self, result, command, name, args,
**handlers_user_args)
except Exception as e:
if exception_handler is not None and \
exception_handler(self, e, **handlers_user_args):
if len(batch_args) < batch_slice_size and \
ci < len(commands) - 1:
# fill in more commands untill batch slice size is reached
# or final slice of commands
continue
self.fail_json(msg="%s: %s: %s" % (command, name, str(e)))
if debug & DEBUG_COMMAND_BATCH:
self.tm_warn("batch %d (size %d/%d)" %
(ci / batch_slice_size, len(batch_args),
batch_slice_size))
# run the batch command
if batch_has_keeponly:
result = api.Command.batch(batch_args, keeponly=keeponly)
else:
result = api.Command.batch(batch_args)
if len(batch_args) != result["count"]:
self.fail_json(
"Result size %d does not match batch size %d" % (
result["count"], len(batch_args)))
if result["count"] > 0:
for ri, res in enumerate(result["results"]):
_res = res.get("result", None)
if not batch_has_keeponly and keeponly is not None \
and isinstance(_res, dict):
res["result"] = dict(
filter(lambda x: x[0] in keeponly,
_res.items())
)
self.tm_warn("res: %s" % repr(res))
if "error" not in res or res["error"] is None:
if result_handler is not None:
result_handler(
self, res,
batch_args[ri]["method"],
batch_args[ri]["params"][0][0],
batch_args[ri]["params"][1],
**handlers_user_args)
changed = True
else:
_errors.append(
"%s %s %s: %s" %
(batch_args[ri]["method"],
repr(batch_args[ri]["params"][0][0]),
repr(batch_args[ri]["params"][1]),
res["error"]))
# clear batch command list (python2 compatible)
del batch_args[:]
else:
# no batch processing
for name, command, args in commands:
try:
if name is None:
result = self.ipa_command_no_name(command, args)
else:
result = self.ipa_command(command, name, args)
if "completed" in result:
if result["completed"] > 0:
changed = True
else:
changed = True
# Handle keeponly
res = result.get("result", None)
if keeponly is not None and isinstance(res, dict):
result["result"] = dict(
filter(lambda x: x[0] in keeponly, res.items())
)
# If result_handler is not None, call it with user args
# defined in **handlers_user_args
if result_handler is not None:
result_handler(self, result, command, name, args,
**handlers_user_args)
except Exception as e:
if exception_handler is not None and \
exception_handler(self, e, **handlers_user_args):
continue
self.fail_json(msg="%s: %s: %s" % (command, name, str(e)))
# Fail on errors from result_handler and exception_handler
if len(_errors) > 0:
self.fail_json(msg=", ".join(_errors))
return changed
def tm_warn(self, warning):
ts = time.time()
# pylint: disable=super-with-arguments
super(IPAAnsibleModule, self).warn("%f %s" % (ts, warning))

View File

@@ -450,6 +450,10 @@ def main():
commands = []
for name in names:
_type = None
inclusive_add, inclusive_del = [], []
exclusive_add, exclusive_del = [], []
# Make sure automember rule exists
res_find = find_automember(ansible_module, name, automember_type)
@@ -495,16 +499,12 @@ def main():
transform_conditions(inclusive),
res_find.get("automemberinclusiveregex", [])
)
else:
inclusive_add, inclusive_del = [], []
if exclusive is not None:
exclusive_add, exclusive_del = gen_add_del_lists(
transform_conditions(exclusive),
res_find.get("automemberexclusiveregex", [])
)
else:
exclusive_add, exclusive_del = [], []
elif action == "member":
if res_find is None:
@@ -512,9 +512,7 @@ def main():
msg="No automember '%s'" % name)
inclusive_add = transform_conditions(inclusive or [])
inclusive_del = []
exclusive_add = transform_conditions(exclusive or [])
exclusive_del = []
for _inclusive in inclusive_add:
key, regex = _inclusive.split("=", 1)

View File

@@ -470,13 +470,13 @@ def main():
"netbios_name": "netbios_name",
"add_sids": "add_sids",
}
allow_empty_string = ["pac_type", "user_auth_type", "configstring"]
reverse_field_map = {v: k for k, v in field_map.items()}
allow_empty_list_item = ["pac_type", "user_auth_type", "configstring"]
params = {}
for x in field_map:
val = ansible_module.params_get(
x, allow_empty_string=x in allow_empty_string)
x, allow_empty_list_item=x in allow_empty_list_item)
if val is not None:
params[field_map.get(x, x)] = val

View File

@@ -180,10 +180,10 @@ def main():
names = ansible_module.params_get("name")
# present
permission = ansible_module.params_get("permission")
attribute = ansible_module.params_get("attribute")
permission = ansible_module.params_get_lowercase("permission")
attribute = ansible_module.params_get_lowercase("attribute")
membergroup = ansible_module.params_get("membergroup")
group = ansible_module.params_get("group")
group = ansible_module.params_get_lowercase("group")
action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state")
@@ -233,6 +233,7 @@ def main():
commands = []
for name in names:
args = {}
# Make sure delegation exists
res_find = find_delegation(ansible_module, name)
@@ -244,14 +245,7 @@ def main():
if action == "delegation":
# Found the delegation
if res_find is not None:
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
if not compare_args_ipa(ansible_module, args,
res_find):
commands.append([name, "delegation_mod", args])
else:
if res_find is None:
commands.append([name, "delegation_add", args])
elif action == "member":
@@ -265,9 +259,7 @@ def main():
# New attribute list (add given ones to find result)
# Make list with unique entries
attrs = list(set(list(res_find["attrs"]) + attribute))
if len(attrs) > len(res_find["attrs"]):
commands.append([name, "delegation_mod",
{"attrs": attrs}])
args["attrs"] = attrs
elif state == "absent":
if action == "delegation":
@@ -288,15 +280,18 @@ def main():
if len(attrs) < 1:
ansible_module.fail_json(
msg="At minimum one attribute is needed.")
# Entries New number of attributes is smaller
if len(attrs) < len(res_find["attrs"]):
commands.append([name, "delegation_mod",
{"attrs": attrs}])
args["attrs"] = attrs
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Manage members
if (
args and res_find and
not compare_args_ipa(ansible_module, args, res_find)
):
commands.append([name, "delegation_mod", args])
# Execute commands
changed = ansible_module.execute_ipa_commands(commands)

View File

@@ -250,6 +250,8 @@ def main():
operation = "add"
invalid = []
wants_enable = False
if state in ["enabled", "disabled"]:
if action == "member":
ansible_module.fail_json(

View File

@@ -1605,6 +1605,8 @@ def main():
res_find = find_dnsrecord(ansible_module, zone_name, name)
cmds = []
if state == 'present':
cmds = define_commands_for_present_state(
ansible_module, zone_name, entry, res_find)

View File

@@ -142,6 +142,11 @@ options:
salt.
required: false
type: str
permission:
description: Set per-zone access delegation permission.
required: false
type: bool
aliases: ["managedby"]
skip_overlap_check:
description: |
Force DNS zone creation even if it will overlap with an existing zone
@@ -154,6 +159,7 @@ options:
author:
- Sergio Oliveira Campos (@seocam)
- Thomas Woerner (@t-woerner)
- Rafael Jeffman (@rjeffman)
""" # noqa: E501
EXAMPLES = """
@@ -253,6 +259,9 @@ class DNSZoneModule(IPAAnsibleModule):
"idnsallowdynupdate": "dynamic_update",
"idnssecinlinesigning": "dnssec",
"idnsupdatepolicy": "update_policy",
# FreeIPA uses 'managedby' for dnszone and dnsforwardzone
# to manage 'permissions'.
"managedby": "permission",
# Mapping by method
"idnsforwarders": self.get_ipa_idnsforwarders,
"idnsallowtransfer": self.get_ipa_idnsallowtransfer,
@@ -434,7 +443,7 @@ class DNSZoneModule(IPAAnsibleModule):
is_zone_active = False
else:
zone = response["result"]
# FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean vaalues.
# FreeIPA 4.9.10+ and 4.10 use proper mapping for boolean values.
# See: https://github.com/freeipa/freeipa/pull/6294
is_zone_active = (
str(zone.get("idnszoneactive")[0]).upper() == "TRUE"
@@ -462,18 +471,24 @@ class DNSZoneModule(IPAAnsibleModule):
self.fail_json(
msg="Either `name` or `name_from_ip` must be provided."
)
# check invalid parameters
invalid = []
if self.ipa_params.state != "present":
invalid = ["name_from_ip"]
self.params_fail_used_invalid(invalid, self.ipa_params.state)
invalid .extend(["name_from_ip"])
if self.ipa_params.state == "absent":
invalid.extend(["permission"])
self.params_fail_used_invalid(invalid, self.ipa_params.state)
def define_ipa_commands(self):
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.ipa_params.get_ipa_command_args(zone=zone)
if self.ipa_params.state in ["present", "enabled", "disabled"]:
args = self.ipa_params.get_ipa_command_args(zone=zone)
# We'll handle "managedby" after dnszone add/mod.
args.pop("managedby", None)
if not zone:
# Since the zone doesn't exist we just create it
# with given args
@@ -487,6 +502,16 @@ class DNSZoneModule(IPAAnsibleModule):
if not compare_args_ipa(self, args, zone):
self.commands.append((zone_name, "dnszone_mod", args))
# Permissions must be set on existing zones.
if self.ipa_params.permission is not None:
is_managed = zone.get("managedby")
if self.ipa_params.permission and not is_managed:
self.commands.append(
(zone_name, "dnszone_add_permission", {}))
if not self.ipa_params.permission and is_managed:
self.commands.append(
(zone_name, "dnszone_remove_permission", {}))
if self.ipa_params.state == "enabled" and not is_zone_active:
self.commands.append((zone_name, "dnszone_enable", {}))
@@ -555,6 +580,8 @@ def get_argument_spec():
ttl=dict(type="int", required=False, default=None),
default_ttl=dict(type="int", required=False, default=None),
nsec3param_rec=dict(type="str", required=False, default=None),
permission=dict(type="bool", required=False, default=None,
aliases=["managedby"]),
skip_nameserver_check=dict(type="bool", required=False, default=None),
skip_overlap_check=dict(type="bool", required=False, default=None),
)

View File

@@ -123,6 +123,11 @@ options:
required: false
type: list
elements: str
rename:
description: Rename the group object
required: false
type: str
aliases: ["new_name"]
description:
description: The group description
type: str
@@ -198,11 +203,16 @@ options:
type: str
default: group
choices: ["member", "group"]
rename:
description: Rename the group object
required: false
type: str
aliases: ["new_name"]
state:
description: State to ensure
type: str
default: present
choices: ["present", "absent"]
choices: ["present", "absent", "renamed"]
author:
- Thomas Woerner (@t-woerner)
"""
@@ -267,6 +277,13 @@ EXAMPLES = """
group:
- group2
# Rename a group
- ipagroup:
ipaadmin_password: SomeADMINpassword
name: oldname
rename: newestname
state: renamed
# Create a non-POSIX group
- ipagroup:
ipaadmin_password: SomeADMINpassword
@@ -380,18 +397,20 @@ def gen_member_args(user, group, service, externalmember, idoverrideuser):
def check_parameters(module, state, action):
invalid = []
if state == "present":
if action == "member":
invalid = ["description", "gid", "posix", "nonposix", "external",
"nomembers"]
else:
invalid = ["description", "gid", "posix", "nonposix", "external",
"nomembers"]
if action == "group":
invalid = ["description", "gid", "posix", "nonposix", "external",
"nomembers"]
if action == "group":
if state == "present":
invalid = []
elif state == "absent":
invalid.extend(["user", "group", "service", "externalmember"])
if state == "renamed":
if action == "member":
module.fail_json(
msg="Action member can not be used with state: renamed.")
invalid.extend(["user", "group", "service", "externalmember"])
else:
invalid.append("rename")
module.params_fail_used_invalid(invalid, state, action)
@@ -448,7 +467,9 @@ def main():
aliases=[
"ipaexternalmember",
"external_member"
])
]),
rename=dict(type="str", required=False, default=None,
aliases=["new_name"]),
)
ansible_module = IPAAnsibleModule(
argument_spec=dict(
@@ -470,7 +491,7 @@ def main():
action=dict(type="str", default="group",
choices=["member", "group"]),
state=dict(type="str", default="present",
choices=["present", "absent"]),
choices=["present", "absent", "renamed"]),
# Add group specific parameters for simple use case
**group_spec
@@ -499,15 +520,19 @@ def main():
idoverrideuser = ansible_module.params_get("idoverrideuser")
posix = ansible_module.params_get("posix")
nomembers = ansible_module.params_get("nomembers")
user = ansible_module.params_get("user")
group = ansible_module.params_get("group")
user = ansible_module.params_get_lowercase("user")
group = ansible_module.params_get_lowercase("group")
# Services are not case sensitive
service = ansible_module.params_get_lowercase("service")
membermanager_user = ansible_module.params_get("membermanager_user")
membermanager_group = ansible_module.params_get("membermanager_group")
membermanager_user = (
ansible_module.params_get_lowercase("membermanager_user"))
membermanager_group = (
ansible_module.params_get_lowercase("membermanager_group"))
externalmember = ansible_module.params_get("externalmember")
# rename
rename = ansible_module.params_get("rename")
# state and action
action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state")
# Check parameters
@@ -516,10 +541,11 @@ def main():
(groups is None or len(groups) < 1):
ansible_module.fail_json(msg="At least one name or groups is required")
if state == "present":
if state in ["present", "renamed"]:
if names is not None and len(names) != 1:
what = "renamed" if state == "renamed" else "added"
ansible_module.fail_json(
msg="Only one group can be added at a time using 'name'.")
msg="Only one group can be %s at a time using 'name'." % what)
check_parameters(ansible_module, state, action)
@@ -633,10 +659,15 @@ def main():
membermanager_group = group_name.get("membermanager_group")
externalmember = group_name.get("externalmember")
nomembers = group_name.get("nomembers")
rename = group_name.get("rename")
check_parameters(ansible_module, state, action)
elif isinstance(group_name, (str, unicode)):
elif (
isinstance(
group_name, (str, unicode) # pylint: disable=W0012,E0606
)
):
name = group_name
else:
ansible_module.fail_json(msg="Group '%s' is not valid" %
@@ -794,6 +825,11 @@ def main():
membermanager_group,
res_find.get("membermanager_group")
)
elif state == "renamed":
if res_find is None:
ansible_module.fail_json(msg="No group '%s'" % name)
elif rename != name:
commands.append([name, 'group_mod', {"rename": rename}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
@@ -868,7 +904,7 @@ def main():
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, fail_on_member_errors=True)
commands, batch=True, keeponly=[], fail_on_member_errors=True)
# Done

View File

@@ -188,13 +188,12 @@ def find_hbacrule(module, name):
elif len(_result["result"]) == 1:
res = _result["result"][0]
# hbacsvcgroup names are converted to lower case while creation with
# hbacsvcgroup_add.
# The hbacsvcgroup for sudo is builtin with the name "Sudo" though.
# This breaks the lower case comparison. Therefore all
# memberservice_hbacsvcgroup items are converted to lower case if
# "Sudo" is in the list.
# hbacsvcgroup_add, but builtin names may have mixed case as "Sudo",
# breaking the lower case comparison. Therefore all
# memberservice_hbacsvcgroup items are converted to lower case.
# (See: https://pagure.io/freeipa/issue/9464).
_member = "memberservice_hbacsvcgroup"
if _member in res and "Sudo" in res[_member]:
if _member in res:
res[_member] = [item.lower() for item in res[_member]]
return res
@@ -400,7 +399,8 @@ def main():
if hbacsvc is not None:
hbacsvc_add, hbacsvc_del = gen_add_del_lists(
hbacsvc, res_find.get("memberservice_hbacsvc"))
hbacsvc, res_find.get("memberservice_hbacsvc"),
)
if hbacsvcgroup is not None:
hbacsvcgroup_add, hbacsvcgroup_del = gen_add_del_lists(

View File

@@ -509,7 +509,8 @@ host:
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, \
encode_certificate, is_ipv4_addr, is_ipv6_addr, ipalib_errors
encode_certificate, is_ipv4_addr, is_ipv6_addr, ipalib_errors, \
gen_add_list, gen_intersection_list, normalize_sshpubkey
from ansible.module_utils import six
if six.PY3:
unicode = str
@@ -533,6 +534,11 @@ def find_host(module, name):
if certs is not None:
_res["usercertificate"] = [encode_certificate(cert) for
cert in certs]
# krbprincipalname is returned as ipapython.kerberos.Principal, convert
# to string
principals = _res.get("krbprincipalname")
if principals is not None:
_res["krbprincipalname"] = [str(princ) for princ in principals]
return _res
@@ -676,8 +682,15 @@ def check_authind(module, auth_ind):
"by your IPA version" % "','".join(_invalid))
def convert_certificate(certificate):
if certificate is None:
return None
return [cert.strip() for cert in certificate]
# pylint: disable=unused-argument
def result_handler(module, result, command, name, args, errors, exit_args,
def result_handler(module, result, command, name, args, exit_args,
single_host):
if "random" in args and command in ["host_add", "host_mod"] \
and "randompassword" in result["result"]:
@@ -688,41 +701,6 @@ def result_handler(module, result, command, name, args, errors, exit_args,
exit_args.setdefault(name, {})["randompassword"] = \
result["result"]["randompassword"]
# All "already a member" and "not a member" failures in the
# result are ignored. All others are reported.
if "failed" in result and len(result["failed"]) > 0:
for item in result["failed"]:
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))
# pylint: disable=unused-argument
def exception_handler(module, ex, errors, exit_args, single_host):
msg = str(ex)
if "already contains" in msg \
or "does not contain" in msg:
return True
# The canonical principal name may not be removed
if "equal to the canonical principal name must" in msg:
return True
# Host is already disabled, ignore error
if "This entry is already disabled" in msg:
return True
# Ignore no modification error.
if "no modifications to be performed" in msg:
return True
return False
def main():
host_spec = dict(
@@ -876,10 +854,11 @@ def main():
allow_retrieve_keytab_hostgroup = ansible_module.params_get(
"allow_retrieve_keytab_hostgroup")
mac_address = ansible_module.params_get("mac_address")
sshpubkey = ansible_module.params_get("sshpubkey",
allow_empty_string=True)
sshpubkey = ansible_module.params_get(
"sshpubkey", allow_empty_list_item=True)
userclass = ansible_module.params_get("userclass")
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True)
auth_ind = ansible_module.params_get(
"auth_ind", allow_empty_list_item=True)
requires_pre_auth = ansible_module.params_get("requires_pre_auth")
ok_as_delegate = ansible_module.params_get("ok_as_delegate")
ok_to_auth_as_delegate = ansible_module.params_get(
@@ -915,6 +894,11 @@ def main():
auth_ind, requires_pre_auth, ok_as_delegate, ok_to_auth_as_delegate,
force, reverse, ip_address, update_dns, update_password)
certificate = convert_certificate(certificate)
if sshpubkey is not None:
sshpubkey = [str(normalize_sshpubkey(key)) for key in sshpubkey]
# Use hosts if names is None
if hosts is not None:
names = hosts
@@ -998,7 +982,15 @@ def main():
ok_to_auth_as_delegate, force, reverse, ip_address,
update_dns, update_password)
elif isinstance(host, (str, unicode)):
certificate = convert_certificate(certificate)
if sshpubkey is not None:
sshpubkey = [str(normalize_sshpubkey(key)) for
key in sshpubkey]
elif (
isinstance(host, (str, unicode)) # pylint: disable=W0012,E0606
):
name = host
else:
ansible_module.fail_json(msg="Host '%s' is not valid" %
@@ -1073,6 +1065,17 @@ def main():
args["krbprincipalauthind"] == ['']:
del args["krbprincipalauthind"]
# Ignore sshpubkey if it is empty (for resetting)
# and not set for the host
if "ipasshpubkey" not in res_find and \
"ipasshpubkey" in args and \
args["ipasshpubkey"] == []:
del args["ipasshpubkey"]
# Ignore updatedns if it is the only arg
if "updatedns" in args and len(args) == 1:
del args["updatedns"]
# For all settings is args, check if there are
# different settings in the find result.
# If yes: modify
@@ -1105,7 +1108,7 @@ def main():
gen_add_del_lists(managedby_host,
res_find.get("managedby_host"))
principal_add, principal_del = gen_add_del_lists(
principal, res_find.get("principal"))
principal, res_find.get("krbprincipalname"))
# Principals are not returned as utf8 for IPA using
# python2 using host_show, therefore we need to
# convert the principals that we should remove.
@@ -1173,50 +1176,115 @@ def main():
gen_add_del_lists(
dnsrecord_args.get("aaaarecord"),
_dnsrec.get("aaaarecord"))
else:
certificate_add = certificate or []
certificate_del = []
managedby_host_add = managedby_host or []
managedby_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 = []
_dnsrec = res_find_dnsrecord or {}
dnsrecord_a_add = gen_add_list(
dnsrecord_args.get("arecord"),
_dnsrec.get("arecord"))
dnsrecord_a_del = []
dnsrecord_aaaa_add = gen_add_list(
dnsrecord_args.get("aaaarecord"),
_dnsrec.get("aaaarecord"))
dnsrecord_aaaa_del = []
else:
# action member
if res_find is None:
ansible_module.fail_json(
msg="No host '%s'" % name)
if action != "host" or (action == "host" and res_find is None):
certificate_add = certificate or []
certificate_add = gen_add_list(
certificate, res_find.get("usercertificate"))
certificate_del = []
managedby_host_add = managedby_host or []
managedby_host_add = gen_add_list(
managedby_host, res_find.get("managedby_host"))
managedby_host_del = []
principal_add = principal or []
principal_add = gen_add_list(
principal, res_find.get("krbprincipalname"))
principal_del = []
allow_create_keytab_user_add = \
allow_create_keytab_user or []
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 = \
allow_create_keytab_group or []
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 = \
allow_create_keytab_host or []
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 = \
allow_create_keytab_hostgroup or []
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 = \
allow_retrieve_keytab_user or []
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 = \
allow_retrieve_keytab_group or []
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 = \
allow_retrieve_keytab_host or []
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 = \
allow_retrieve_keytab_hostgroup or []
allow_retrieve_keytab_hostgroup_add = gen_add_list(
allow_retrieve_keytab_hostgroup,
res_find.get(
"ipaallowedtoperform_read_keys_hostgroup"))
allow_retrieve_keytab_hostgroup_del = []
dnsrecord_a_add = dnsrecord_args.get("arecord") or []
_dnsrec = res_find_dnsrecord or {}
dnsrecord_a_add = gen_add_list(
dnsrecord_args.get("arecord"),
_dnsrec.get("arecord"))
dnsrecord_a_del = []
dnsrecord_aaaa_add = dnsrecord_args.get("aaaarecord") or []
dnsrecord_aaaa_add = gen_add_list(
dnsrecord_args.get("aaaarecord"),
_dnsrec.get("aaaarecord"))
dnsrecord_aaaa_del = []
# Remove canonical principal from principal_del
canonical_principal = "host/" + name + "@" + server_realm
# canonical_principal is also in find_res["krbcanonicalname"]
if canonical_principal in principal_del and \
action == "host" and (principal is not None or
canonical_principal not in principal):
@@ -1397,8 +1465,10 @@ def main():
# the removal of non-existing entries.
# Remove certificates
if certificate is not None:
for _certificate in certificate:
certificate_del = gen_intersection_list(
certificate, res_find.get("usercertificate"))
if certificate_del is not None:
for _certificate in certificate_del:
commands.append([name, "host_remove_cert",
{
"usercertificate":
@@ -1411,8 +1481,10 @@ def main():
# the removal of non-existing entries.
# Remove managedby_hosts
if managedby_host is not None:
for _managedby_host in managedby_host:
managedby_host_del = gen_intersection_list(
managedby_host, res_find.get("managedby_host"))
if managedby_host_del is not None:
for _managedby_host in managedby_host_del:
commands.append([name, "host_remove_managedby",
{
"host":
@@ -1425,8 +1497,10 @@ def main():
# the removal of non-existing entries.
# Remove principals
if principal is not None:
for _principal in principal:
principal_del = gen_intersection_list(
principal, res_find.get("krbprincipalname"))
if principal_del is not None:
for _principal in principal_del:
commands.append([name, "host_remove_principal",
{
"krbprincipalname":
@@ -1434,60 +1508,86 @@ def main():
}])
# Disallow create keytab
if allow_create_keytab_user is not None or \
allow_create_keytab_group is not None or \
allow_create_keytab_host is not None or \
allow_create_keytab_hostgroup is not None:
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"))
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, "host_disallow_create_keytab",
{
"user": allow_create_keytab_user,
"group": allow_create_keytab_group,
"host": allow_create_keytab_host,
"hostgroup": allow_create_keytab_hostgroup,
"user": allow_create_keytab_user_del,
"group": allow_create_keytab_group_del,
"host": allow_create_keytab_host_del,
"hostgroup":
allow_create_keytab_hostgroup_del,
}])
# Disallow retrieve keytab
if allow_retrieve_keytab_user is not None or \
allow_retrieve_keytab_group is not None or \
allow_retrieve_keytab_host is not None or \
allow_retrieve_keytab_hostgroup is not None:
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"))
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, "host_disallow_retrieve_keytab",
{
"user": allow_retrieve_keytab_user,
"group": allow_retrieve_keytab_group,
"host": allow_retrieve_keytab_host,
"hostgroup": allow_retrieve_keytab_hostgroup,
"user": allow_retrieve_keytab_user_del,
"group": allow_retrieve_keytab_group_del,
"host": allow_retrieve_keytab_host_del,
"hostgroup":
allow_retrieve_keytab_hostgroup_del,
}])
dnsrecord_args = gen_dnsrecord_args(ansible_module,
ip_address, reverse)
if res_find_dnsrecord is not None:
dnsrecord_args = gen_dnsrecord_args(
ansible_module, ip_address, reverse)
# Remove arecord and aaaarecord from dnsrecord_args
# if the record does not exits in res_find_dnsrecord
# to prevent "DNS resource record not found" error
if "arecord" in dnsrecord_args \
and dnsrecord_args["arecord"] is not None \
and len(dnsrecord_args["arecord"]) > 0 \
and (res_find_dnsrecord is None
or "arecord" not in res_find_dnsrecord):
del dnsrecord_args["arecord"]
if "aaaarecord" in dnsrecord_args \
and dnsrecord_args["aaaarecord"] is not None \
and len(dnsrecord_args["aaaarecord"]) > 0 \
and (res_find_dnsrecord is None
or "aaaarecord" not in res_find_dnsrecord):
del dnsrecord_args["aaaarecord"]
# Only keep a and aaaa recrords that are part
# of res_find_dnsrecord.
for _type in ["arecord", "aaaarecord"]:
if _type in dnsrecord_args:
recs = gen_intersection_list(
dnsrecord_args[_type],
res_find_dnsrecord.get(_type))
if len(recs) > 0:
dnsrecord_args[_type] = recs
else:
del dnsrecord_args[_type]
if "arecord" in dnsrecord_args or \
"aaaarecord" in dnsrecord_args:
domain_name = name[name.find(".") + 1:]
host_name = name[:name.find(".")]
dnsrecord_args["idnsname"] = host_name
if "arecord" in dnsrecord_args or \
"aaaarecord" in dnsrecord_args:
domain_name = name[name.find(".") + 1:]
host_name = name[:name.find(".")]
dnsrecord_args["idnsname"] = host_name
commands.append([domain_name, "dnsrecord_del",
dnsrecord_args])
commands.append([domain_name, "dnsrecord_del",
dnsrecord_args])
elif state == "disabled":
if res_find is not None:
@@ -1503,7 +1603,7 @@ def main():
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, result_handler, exception_handler,
commands, result_handler, batch=True, keeponly=["randompassword"],
exit_args=exit_args, single_host=hosts is None)
# Done

View File

@@ -181,16 +181,6 @@ def gen_args(description, nomembers, rename):
return _args
def gen_member_args(host, hostgroup):
_args = {}
if host is not None:
_args["member_host"] = host
if hostgroup is not None:
_args["member_hostgroup"] = hostgroup
return _args
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
@@ -224,16 +214,20 @@ def main():
# Get parameters
# general
names = ansible_module.params_get("name")
names = ansible_module.params_get_lowercase("name")
# present
description = ansible_module.params_get("description")
nomembers = ansible_module.params_get("nomembers")
host = ansible_module.params_get("host")
hostgroup = ansible_module.params_get("hostgroup")
membermanager_user = ansible_module.params_get("membermanager_user")
membermanager_group = ansible_module.params_get("membermanager_group")
rename = ansible_module.params_get("rename")
hostgroup = ansible_module.params_get_lowercase("hostgroup")
membermanager_user = ansible_module.params_get_lowercase(
"membermanager_user"
)
membermanager_group = ansible_module.params_get_lowercase(
"membermanager_group"
)
rename = ansible_module.params_get_lowercase("rename")
action = ansible_module.params_get("action")
# state
state = ansible_module.params_get("state")
@@ -306,6 +300,12 @@ def main():
commands = []
for name in names:
# clean add/del lists
host_add, host_del = [], []
hostgroup_add, hostgroup_del = [], []
membermanager_user_add, membermanager_user_del = [], []
membermanager_group_add, membermanager_group_del = [], []
# Make sure hostgroup exists
res_find = find_hostgroup(ansible_module, name)
@@ -328,63 +328,26 @@ def main():
# Set res_find to empty dict for next step
res_find = {}
member_args = gen_member_args(host, hostgroup)
if not compare_args_ipa(ansible_module, member_args,
res_find):
# Generate addition and removal lists
host_add, host_del = gen_add_del_lists(
host, res_find.get("member_host"))
# Generate addition and removal lists
host_add, host_del = gen_add_del_lists(
host, res_find.get("member_host")
)
hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get("member_hostgroup"))
# Add members
if len(host_add) > 0 or len(hostgroup_add) > 0:
commands.append([name, "hostgroup_add_member",
{
"host": host_add,
"hostgroup": hostgroup_add,
}])
# Remove members
if len(host_del) > 0 or len(hostgroup_del) > 0:
commands.append([name, "hostgroup_remove_member",
{
"host": host_del,
"hostgroup": hostgroup_del,
}])
membermanager_user_add, membermanager_user_del = \
gen_add_del_lists(
membermanager_user,
res_find.get("membermanager_user")
)
membermanager_group_add, membermanager_group_del = \
gen_add_del_lists(
membermanager_group,
res_find.get("membermanager_group")
)
hostgroup_add, hostgroup_del = gen_add_del_lists(
hostgroup, res_find.get("member_hostgroup")
)
if has_add_membermanager:
# Add membermanager users and groups
if len(membermanager_user_add) > 0 or \
len(membermanager_group_add) > 0:
commands.append(
[name, "hostgroup_add_member_manager",
{
"user": membermanager_user_add,
"group": membermanager_group_add,
}]
membermanager_user_add, membermanager_user_del = \
gen_add_del_lists(
membermanager_user,
res_find.get("membermanager_user")
)
# Remove member manager
if len(membermanager_user_del) > 0 or \
len(membermanager_group_del) > 0:
commands.append(
[name, "hostgroup_remove_member_manager",
{
"user": membermanager_user_del,
"group": membermanager_group_del,
}]
membermanager_group_add, membermanager_group_del = \
gen_add_del_lists(
membermanager_group,
res_find.get("membermanager_group")
)
elif action == "member":
@@ -394,45 +357,25 @@ def main():
# Reduce add lists for member_host and member_hostgroup,
# to new entries only that are not in res_find.
if host is not None and "member_host" in res_find:
host = gen_add_list(host, res_find["member_host"])
if hostgroup is not None \
and "member_hostgroup" in res_find:
hostgroup = gen_add_list(
hostgroup, res_find["member_hostgroup"])
# Ensure members are present
commands.append([name, "hostgroup_add_member",
{
"host": host,
"hostgroup": hostgroup,
}])
host_add = gen_add_list(
host, res_find.get("member_host")
)
hostgroup_add = gen_add_list(
hostgroup, res_find.get("member_hostgroup")
)
if has_add_membermanager:
# Reduce add list for membermanager_user and
# membermanager_group to new entries only that are
# not in res_find.
if membermanager_user is not None \
and "membermanager_user" in res_find:
membermanager_user = gen_add_list(
membermanager_user,
res_find["membermanager_user"])
if membermanager_group is not None \
and "membermanager_group" in res_find:
membermanager_group = gen_add_list(
membermanager_group,
res_find["membermanager_group"])
# Add membermanager users and groups
if membermanager_user is not None or \
membermanager_group is not None:
commands.append(
[name, "hostgroup_add_member_manager",
{
"user": membermanager_user,
"group": membermanager_group,
}]
)
membermanager_user_add = gen_add_list(
membermanager_user,
res_find.get("membermanager_user")
)
membermanager_group_add = gen_add_list(
membermanager_group,
res_find.get("membermanager_group")
)
elif state == "renamed":
if res_find is not None:
@@ -463,46 +406,72 @@ def main():
# Reduce del lists of member_host and member_hostgroup,
# to the entries only that are in res_find.
if host is not None:
host = gen_intersection_list(
host, res_find.get("member_host"))
host_del = gen_intersection_list(
host, res_find.get("member_host")
)
if hostgroup is not None:
hostgroup = gen_intersection_list(
hostgroup, res_find.get("member_hostgroup"))
# Ensure members are absent
commands.append([name, "hostgroup_remove_member",
{
"host": host,
"hostgroup": hostgroup,
}])
hostgroup_del = gen_intersection_list(
hostgroup, res_find.get("member_hostgroup")
)
if has_add_membermanager:
# Reduce del lists of membermanager_user and
# membermanager_group to the entries only that are
# in res_find.
if membermanager_user is not None:
membermanager_user = gen_intersection_list(
membermanager_user,
res_find.get("membermanager_user"))
if membermanager_group is not None:
membermanager_group = gen_intersection_list(
membermanager_group,
res_find.get("membermanager_group"))
# Remove membermanager users and groups
if membermanager_user is not None or \
membermanager_group is not None:
commands.append(
[name, "hostgroup_remove_member_manager",
{
"user": membermanager_user,
"group": membermanager_group,
}]
)
# Get lists of membermanager users that exist
# in IPA and should be removed.
membermanager_user_del = gen_intersection_list(
membermanager_user,
res_find.get("membermanager_user")
)
membermanager_group_del = gen_intersection_list(
membermanager_group,
res_find.get("membermanager_group")
)
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
# Manage members
# Add members
if host_add or hostgroup_add:
commands.append([
name, "hostgroup_add_member",
{
"host": host_add,
"hostgroup": hostgroup_add,
}
])
# Remove members
if host_del or hostgroup_del:
commands.append([
name, "hostgroup_remove_member",
{
"host": host_del,
"hostgroup": hostgroup_del,
}
])
# Manage membermanager users and groups
if has_add_membermanager:
# Add membermanager users and groups
if membermanager_user_add or membermanager_group_add:
commands.append([
name, "hostgroup_add_member_manager",
{
"user": membermanager_user_add,
"group": membermanager_group_add,
}
])
# Remove membermanager users and groups
if membermanager_user_del or membermanager_group_del:
commands.append([
name, "hostgroup_remove_member_manager",
{
"user": membermanager_user_del,
"group": membermanager_group_del,
}
])
# Execute commands
changed = ansible_module.execute_ipa_commands(

View File

@@ -243,7 +243,7 @@ def main():
# present
description = ansible_module.params_get("description")
name = ansible_module.params_get("name")
gid = ansible_module.params_get("gid")
gid = ansible_module.params_get_with_type_cast("gid", int)
# runtime flags
fallback_to_ldap = ansible_module.params_get("fallback_to_ldap")
@@ -271,19 +271,6 @@ def main():
ansible_module.params_fail_used_invalid(invalid, state)
# Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
gid = int_or_empty_param(gid, "gid")
# Init
changed = False

View File

@@ -439,9 +439,9 @@ def main():
# present
description = ansible_module.params_get("description")
name = ansible_module.params_get("name")
uid = ansible_module.params_get("uid")
uid = ansible_module.params_get_with_type_cast("uid", int)
gecos = ansible_module.params_get("gecos")
gidnumber = ansible_module.params_get("gidnumber")
gidnumber = ansible_module.params_get_with_type_cast("gidnumber", int)
homedir = ansible_module.params_get("homedir")
shell = ansible_module.params_get("shell")
sshpubkey = ansible_module.params_get("sshpubkey")
@@ -479,20 +479,6 @@ def main():
ansible_module.params_fail_used_invalid(invalid, state, action)
# Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
uid = int_or_empty_param(uid, "uid")
gidnumber = int_or_empty_param(gidnumber, "gidnumber")
if certificate is not None:
certificate = [cert.strip() for cert in certificate]

View File

@@ -185,7 +185,7 @@ RETURN = """
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, template_str
IPAAnsibleModule, compare_args_ipa, template_str, urlparse
from ansible.module_utils import six
from copy import deepcopy
import string
@@ -274,7 +274,14 @@ def find_idp(module, name):
# An exception is raised if idp name is not found.
return None
return _result["result"]
res = _result["result"]
# Decode binary string secret
if "ipaidpclientsecret" in res and len(res["ipaidpclientsecret"]) > 0:
res["ipaidpclientsecret"][0] = \
res["ipaidpclientsecret"][0].decode("ascii")
return res
def gen_args(auth_uri, dev_auth_uri, token_uri, userinfo_uri, keys_uri,
@@ -333,6 +340,16 @@ def convert_provider_to_endpoints(module, _args, provider):
_args.update(points)
def validate_uri(module, uri):
try:
parsed = urlparse(uri, 'https')
except Exception:
module.fail_json(msg="Invalid URI '%s': not an https scheme" % uri)
if not parsed.netloc:
module.fail_json(msg="Invalid URI '%s': missing netloc" % uri)
def main():
ansible_module = IPAAnsibleModule(
argument_spec=dict(
@@ -426,19 +443,6 @@ def main():
if provider not in idp_providers:
ansible_module.fail_json(
msg="Provider '%s' is unknown" % provider)
else:
if not auth_uri:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "auth_uri")
if not dev_auth_uri:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "dev_auth_uri")
if not token_uri:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "token_uri")
if not userinfo_uri:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "userinfo_uri")
invalid = ["rename", "delete_continue"]
else:
# state renamed and absent
@@ -459,6 +463,19 @@ def main():
ansible_module.params_fail_used_invalid(invalid, state)
# Empty client_id test
if client_id is not None and client_id == "":
ansible_module.fail_json(msg="'client_id' is required")
# Normalize base_url
if base_url is not None and base_url.startswith('https://'):
base_url = base_url[len('https://'):]
# Validate uris
for uri in [auth_uri, dev_auth_uri, token_uri, userinfo_uri, keys_uri]:
if uri is not None and uri != "":
validate_uri(ansible_module, uri)
# Init
changed = False
@@ -507,6 +524,19 @@ def main():
res_find):
commands.append([name, "idp_mod", args])
else:
if "ipaidpauthendpoint" not in args:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "auth_uri")
if "ipaidpdevauthendpoint" not in args:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "dev_auth_uri")
if "ipaidptokenendpoint" not in args:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "token_uri")
if "ipaidpuserinfoendpoint" not in args:
ansible_module.fail_json(
msg="Parameter '%s' is missing" % "userinfo_uri")
commands.append([name, "idp_add", args])
elif state == "absent":

View File

@@ -153,7 +153,7 @@ RETURN = """
"""
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, boolean
IPAAnsibleModule, compare_args_ipa
def find_pwpolicy(module, name):
@@ -294,20 +294,34 @@ def main():
names = ansible_module.params_get("name")
# present
maxlife = ansible_module.params_get("maxlife")
minlife = ansible_module.params_get("minlife")
history = ansible_module.params_get("history")
minclasses = ansible_module.params_get("minclasses")
minlength = ansible_module.params_get("minlength")
priority = ansible_module.params_get("priority")
maxfail = ansible_module.params_get("maxfail")
failinterval = ansible_module.params_get("failinterval")
lockouttime = ansible_module.params_get("lockouttime")
maxrepeat = ansible_module.params_get("maxrepeat")
maxsequence = ansible_module.params_get("maxsequence")
dictcheck = ansible_module.params_get("dictcheck")
usercheck = ansible_module.params_get("usercheck")
gracelimit = ansible_module.params_get("gracelimit")
maxlife = ansible_module.params_get_with_type_cast(
"maxlife", int, allow_empty=True)
minlife = ansible_module.params_get_with_type_cast(
"minlife", int, allow_empty=True)
history = ansible_module.params_get_with_type_cast(
"history", int, allow_empty=True)
minclasses = ansible_module.params_get_with_type_cast(
"minclasses", int, allow_empty=True)
minlength = ansible_module.params_get_with_type_cast(
"minlength", int, allow_empty=True)
priority = ansible_module.params_get_with_type_cast(
"priority", int, allow_empty=True)
maxfail = ansible_module.params_get_with_type_cast(
"maxfail", int, allow_empty=True)
failinterval = ansible_module.params_get_with_type_cast(
"failinterval", int, allow_empty=True)
lockouttime = ansible_module.params_get_with_type_cast(
"lockouttime", int, allow_empty=True)
maxrepeat = ansible_module.params_get_with_type_cast(
"maxrepeat", int, allow_empty=True)
maxsequence = ansible_module.params_get_with_type_cast(
"maxsequence", int, allow_empty=True)
dictcheck = ansible_module.params_get_with_type_cast(
"dictcheck", bool, allow_empty=True)
usercheck = ansible_module.params_get_with_type_cast(
"usercheck", bool, allow_empty=True)
gracelimit = ansible_module.params_get_with_type_cast(
"gracelimit", int, allow_empty=True)
# state
state = ansible_module.params_get("state")
@@ -336,41 +350,6 @@ def main():
ansible_module.params_fail_used_invalid(invalid, state)
# Ensure parameter values are valid and have proper type.
def int_or_empty_param(value, param):
if value is not None and value != "":
try:
value = int(value)
except ValueError:
ansible_module.fail_json(
msg="Invalid value '%s' for argument '%s'" % (value, param)
)
return value
maxlife = int_or_empty_param(maxlife, "maxlife")
minlife = int_or_empty_param(minlife, "minlife")
history = int_or_empty_param(history, "history")
minclasses = int_or_empty_param(minclasses, "minclasses")
minlength = int_or_empty_param(minlength, "minlength")
priority = int_or_empty_param(priority, "priority")
maxfail = int_or_empty_param(maxfail, "maxfail")
failinterval = int_or_empty_param(failinterval, "failinterval")
lockouttime = int_or_empty_param(lockouttime, "lockouttime")
maxrepeat = int_or_empty_param(maxrepeat, "maxrepeat")
maxsequence = int_or_empty_param(maxsequence, "maxsequence")
gracelimit = int_or_empty_param(gracelimit, "gracelimit")
def bool_or_empty_param(value, param): # pylint: disable=R1710
if value is None or value == "":
return value
try:
return boolean(value)
except TypeError as terr:
ansible_module.fail_json(msg="Param '%s': %s" % (param, str(terr)))
dictcheck = bool_or_empty_param(dictcheck, "dictcheck")
usercheck = bool_or_empty_param(usercheck, "usercheck")
# Ensure gracelimit has proper limit.
if gracelimit:
if gracelimit < -1:

View File

@@ -293,7 +293,7 @@ def result_get_value_lowercase(res_find, key, default=None):
if existing is not None:
if isinstance(existing, (list, tuple)):
existing = [to_text(item).lower() for item in existing]
if isinstance(existing, (str, unicode)):
if isinstance(existing, (str, unicode)): # pylint: disable=W0012,E0606
existing = existing.lower()
else:
existing = default

View File

@@ -607,8 +607,10 @@ def main():
# white space also.
if certificate is not None:
certificate = [cert.strip() for cert in certificate]
pac_type = ansible_module.params_get("pac_type", allow_empty_string=True)
auth_ind = ansible_module.params_get("auth_ind", allow_empty_string=True)
pac_type = ansible_module.params_get(
"pac_type", allow_empty_list_item=True)
auth_ind = ansible_module.params_get(
"auth_ind", allow_empty_list_item=True)
skip_host_check = ansible_module.params_get("skip_host_check")
force = ansible_module.params_get("force")
requires_pre_auth = ansible_module.params_get("requires_pre_auth")
@@ -691,7 +693,11 @@ def main():
delete_continue = service.get("delete_continue")
elif isinstance(service, (str, unicode)):
elif (
isinstance(
service, (str, unicode) # pylint: disable=W0012,E0606
)
):
name = service
else:
ansible_module.fail_json(msg="Service '%s' is not valid" %
@@ -838,7 +844,9 @@ def main():
elif state == "absent":
if action == "service":
if res_find is not None:
args = {'continue': delete_continue}
args = {}
if delete_continue is not None:
args['continue'] = delete_continue
commands.append([name, 'service_del', args])
elif action == "member":
@@ -927,7 +935,7 @@ def main():
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, fail_on_member_errors=True)
commands, batch=True, keeponly=[], fail_on_member_errors=True)
# Done
ansible_module.exit_json(changed=changed, **exit_args)

View File

@@ -138,6 +138,11 @@ options:
required: false
type: list
elements: str
runasuser_group:
description: List of groups for Sudo to execute as.
required: false
type: list
elements: str
runasgroup:
description: List of groups for Sudo to execute as.
required: false
@@ -214,6 +219,12 @@ EXAMPLES = """
hostmask:
- 192.168.122.1/24
- 192.168.120.1/24
# Ensure sudorule 'runasuser' has 'ipasuers' group as runas users.
- ipasudorule:
ipaadmin_password: SomeADMINpassword
name: testrule1
runasuser_group: ipausers
action: member
# Ensure Sudo Rule tesrule1 is absent
@@ -315,6 +326,8 @@ def main():
default=None),
runasgroup=dict(required=False, type="list", elements="str",
default=None),
runasuser_group=dict(required=False, type="list", elements="str",
default=None),
order=dict(type="int", required=False, aliases=['sudoorder']),
sudooption=dict(required=False, type='list', elements="str",
default=None, aliases=["options"]),
@@ -362,6 +375,7 @@ def main():
sudooption = ansible_module.params_get("sudooption")
order = ansible_module.params_get("order")
runasuser = ansible_module.params_get_lowercase("runasuser")
runasuser_group = ansible_module.params_get_lowercase("runasuser_group")
runasgroup = ansible_module.params_get_lowercase("runasgroup")
action = ansible_module.params_get("action")
@@ -406,7 +420,8 @@ def main():
invalid.extend(["host", "hostgroup", "hostmask", "user", "group",
"runasuser", "runasgroup", "allow_sudocmd",
"allow_sudocmdgroup", "deny_sudocmd",
"deny_sudocmdgroup", "sudooption"])
"deny_sudocmdgroup", "sudooption",
"runasuser_group"])
elif state in ["enabled", "disabled"]:
if len(names) < 1:
@@ -420,7 +435,7 @@ def main():
"nomembers", "nomembers", "host", "hostgroup", "hostmask",
"user", "group", "allow_sudocmd", "allow_sudocmdgroup",
"deny_sudocmd", "deny_sudocmdgroup", "runasuser",
"runasgroup", "order", "sudooption"]
"runasgroup", "order", "sudooption", "runasuser_group"]
else:
ansible_module.fail_json(msg="Invalid state '%s'" % state)
@@ -453,6 +468,7 @@ def main():
deny_cmdgroup_add, deny_cmdgroup_del = [], []
sudooption_add, sudooption_del = [], []
runasuser_add, runasuser_del = [], []
runasuser_group_add, runasuser_group_del = [], []
runasgroup_add, runasgroup_del = [], []
for name in names:
@@ -552,6 +568,12 @@ def main():
+ res_find.get('ipasudorunasextuser', [])
)
)
runasuser_group_add, runasuser_group_del = (
gen_add_del_lists(
runasuser_group,
res_find.get('ipasudorunas_group', [])
)
)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups. IPA will handle the correct
@@ -623,6 +645,11 @@ def main():
(list(res_find.get('ipasudorunas_user', []))
+ list(res_find.get('ipasudorunasextuser', [])))
)
if runasuser_group is not None:
runasuser_group_add = gen_add_list(
runasuser_group,
res_find.get('ipasudorunas_group', [])
)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare
# the provided list against both users and external
@@ -703,6 +730,11 @@ def main():
+ list(res_find.get('ipasudorunasextuser', []))
)
)
if runasuser_group is not None:
runasuser_group_del = gen_intersection_list(
runasuser_group,
res_find.get('ipasudorunas_group', [])
)
# runasgroup attribute can be used with both IPA and
# non-IPA (external) groups, so we need to compare
# the provided list against both groups and external
@@ -812,13 +844,19 @@ def main():
}
])
# Manage RunAS users
if runasuser_add:
if runasuser_add or runasuser_group_add:
# Can't use empty lists with command "sudorule_add_runasuser".
_args = {}
if runasuser_add:
_args["user"] = runasuser_add
if runasuser_group_add:
_args["group"] = runasuser_group_add
commands.append([name, "sudorule_add_runasuser", _args])
if runasuser_del or runasuser_group_del:
commands.append([
name, "sudorule_add_runasuser", {"user": runasuser_add}
])
if runasuser_del:
commands.append([
name, "sudorule_remove_runasuser", {"user": runasuser_del}
name,
"sudorule_remove_runasuser",
{"user": runasuser_del, "group": runasuser_group_del}
])
# Manage RunAS Groups

View File

@@ -319,6 +319,11 @@ options:
description: Suppress processing of membership attributes
required: false
type: bool
rename:
description: Rename the user object
required: false
type: str
aliases: ["new_name"]
required: false
first:
description: The first name. Required if user does not exist.
@@ -586,6 +591,11 @@ options:
description: Suppress processing of membership attributes
required: false
type: bool
rename:
description: Rename the user object
required: false
type: str
aliases: ["new_name"]
preserve:
description: Delete a user, keeping the entry available for future use
required: false
@@ -607,7 +617,8 @@ options:
default: present
choices: ["present", "absent",
"enabled", "disabled",
"unlocked", "undeleted"]
"unlocked", "undeleted",
"renamed"]
author:
- Thomas Woerner (@t-woerner)
"""
@@ -694,6 +705,13 @@ EXAMPLES = """
smb_profile_path: \\\\server\\profiles\\some_profile
smb_home_dir: \\\\users\\home\\smbuser
smb_home_drive: "U:"
# Rename an existing user
- ipauser:
ipaadmin_password: SomeADMINpassword
name: someuser
rename: anotheruser
state: renamed
"""
RETURN = """
@@ -723,7 +741,7 @@ user:
from ansible.module_utils.ansible_freeipa_module import \
IPAAnsibleModule, compare_args_ipa, gen_add_del_lists, date_format, \
encode_certificate, load_cert_from_str, DN_x500_text, to_text, \
ipalib_errors
ipalib_errors, gen_add_list, gen_intersection_list
from ansible.module_utils import six
if six.PY3:
unicode = str
@@ -857,7 +875,7 @@ def check_parameters( # pylint: disable=unused-argument
employeenumber, employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve, update_password,
smb_logon_script, smb_profile_path, smb_home_dir, smb_home_drive,
idp, ipa_user_id,
idp, ipa_user_id, rename
):
if state == "present" and action == "user":
invalid = ["preserve"]
@@ -885,6 +903,19 @@ def check_parameters( # pylint: disable=unused-argument
module.fail_json(
msg="Preserve is only possible for state=absent")
if state != "renamed":
invalid.append("rename")
else:
invalid.extend([
"preserve", "principal", "manager", "certificate", "certmapdata",
])
if not rename:
module.fail_json(
msg="A value for attribute 'rename' must be provided.")
if action == "member":
module.fail_json(
msg="Action member can not be used with state: renamed.")
module.params_fail_used_invalid(invalid, state, action)
if certmapdata is not None:
@@ -930,6 +961,13 @@ def extend_emails(email, default_email_domain):
return email
def convert_certificate(certificate):
if certificate is None:
return None
return [cert.strip() for cert in certificate]
def convert_certmapdata(certmapdata):
if certmapdata is None:
return None
@@ -975,9 +1013,8 @@ def gen_certmapdata_args(certmapdata):
# pylint: disable=unused-argument
def result_handler(module, result, command, name, args, errors, exit_args,
single_user):
def result_handler(module, result, command, name, args, exit_args,
errors, single_user):
if "random" in args and command in ["user_add", "user_mod"] \
and "randompassword" in result["result"]:
if single_user:
@@ -987,31 +1024,8 @@ def result_handler(module, result, command, name, args, errors, exit_args,
exit_args.setdefault(name, {})["randompassword"] = \
result["result"]["randompassword"]
# Get all errors
# All "already a member" and "not a member" failures in the
# result are ignored. All others are reported.
if "failed" in result and len(result["failed"]) > 0:
for item in result["failed"]:
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))
# pylint: disable=unused-argument
def exception_handler(module, ex, errors, exit_args, single_user):
msg = str(ex)
if "already contains" in msg \
or "does not contain" in msg:
return True
# The canonical principal name may not be removed
if "equal to the canonical principal name must" in msg:
return True
return False
IPAAnsibleModule.member_error_handler(module, result, command, name, args,
errors)
def main():
@@ -1097,6 +1111,8 @@ def main():
idp=dict(type="str", default=None, aliases=['ipaidpconfiglink']),
idp_user_id=dict(type="str", default=None,
aliases=['ipaidpconfiglink']),
rename=dict(type="str", required=False, default=None,
aliases=["new_name"]),
)
ansible_module = IPAAnsibleModule(
@@ -1128,7 +1144,7 @@ def main():
choices=["member", "user"]),
state=dict(type="str", default="present",
choices=["present", "absent", "enabled", "disabled",
"unlocked", "undeleted"]),
"unlocked", "undeleted", "renamed"]),
# Add user specific parameters for simple use case
**user_spec
@@ -1185,9 +1201,9 @@ def main():
manager = ansible_module.params_get("manager")
carlicense = ansible_module.params_get("carlicense")
sshpubkey = ansible_module.params_get("sshpubkey",
allow_empty_string=True)
allow_empty_list_item=True)
userauthtype = ansible_module.params_get("userauthtype",
allow_empty_string=True)
allow_empty_list_item=True)
userclass = ansible_module.params_get("userclass")
radius = ansible_module.params_get("radius")
radiususer = ansible_module.params_get("radiususer")
@@ -1209,6 +1225,8 @@ def main():
preserve = ansible_module.params_get("preserve")
# mod
update_password = ansible_module.params_get("update_password")
# rename
rename = ansible_module.params_get("rename")
# general
action = ansible_module.params_get("action")
state = ansible_module.params_get("state")
@@ -1219,27 +1237,31 @@ def main():
(users is None or len(users) < 1):
ansible_module.fail_json(msg="One of name and users is required")
if state == "present":
if state in ["present", "renamed"]:
if names is not None and len(names) != 1:
act = "renamed" if state == "renamed" else "added"
ansible_module.fail_json(
msg="Only one user can be added at a time using name.")
check_parameters(
ansible_module, state, action,
first, last, fullname, displayname, initials, homedir, gecos, shell,
email,
principal, principalexpiration, passwordexpiration, password, random,
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
preserve, update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id)
certmapdata = convert_certmapdata(certmapdata)
msg="Only one user can be %s at a time using name." % (act))
# Use users if names is None
if users is not None:
names = users
else:
check_parameters(
ansible_module, state, action,
first, last, fullname, displayname, initials, homedir, gecos,
shell, email,
principal, principalexpiration, passwordexpiration, password,
random,
uid, gid, street, city, phone, mobile, pager, fax, orgunit, title,
manager, carlicense, sshpubkey, userauthtype, userclass, radius,
radiususer, departmentnumber, employeenumber, employeetype,
preferredlanguage, certificate, certmapdata, noprivate, nomembers,
preserve, update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
)
certificate = convert_certificate(certificate)
certmapdata = convert_certmapdata(certmapdata)
# Init
@@ -1330,6 +1352,7 @@ def main():
smb_home_drive = user.get("smb_home_drive")
idp = user.get("idp")
idp_user_id = user.get("idp_user_id")
rename = user.get("rename")
certificate = user.get("certificate")
certmapdata = user.get("certmapdata")
noprivate = user.get("noprivate")
@@ -1346,7 +1369,9 @@ def main():
employeetype, preferredlanguage, certificate,
certmapdata, noprivate, nomembers, preserve,
update_password, smb_logon_script, smb_profile_path,
smb_home_dir, smb_home_drive, idp, idp_user_id)
smb_home_dir, smb_home_drive, idp, idp_user_id, rename,
)
certificate = convert_certificate(certificate)
certmapdata = convert_certmapdata(certmapdata)
# Check API specific parameters
@@ -1357,7 +1382,11 @@ def main():
email = extend_emails(email, default_email_domain)
elif isinstance(user, (str, unicode)):
elif (
isinstance(
user, (str, unicode) # pylint: disable=W0012,E0606
)
):
name = user
else:
ansible_module.fail_json(msg="User '%s' is not valid" %
@@ -1449,6 +1478,10 @@ def main():
del args["userpassword"]
if "random" in args:
del args["random"]
# if using "random:false" password should not be
# generated.
if not args.get("random", True):
del args["random"]
if "noprivate" in args:
del args["noprivate"]
@@ -1602,10 +1635,12 @@ def main():
msg="No user '%s'" % name)
# Ensure managers are present
if manager is not None and len(manager) > 0:
manager_add = gen_add_list(
manager, res_find.get("manager"))
if manager_add is not None and len(manager_add) > 0:
commands.append([name, "user_add_manager",
{
"user": manager,
"user": manager_add,
}])
# Principals need to be added and removed one by one,
@@ -1614,8 +1649,10 @@ def main():
# the removal of non-existing entries.
# Ensure principals are present
if principal is not None and len(principal) > 0:
for _principal in principal:
principal_add = gen_add_list(
principal, res_find.get("krbprincipalname"))
if principal_add is not None and len(principal_add) > 0:
for _principal in principal_add:
commands.append([name, "user_add_principal",
{
"krbprincipalname":
@@ -1628,8 +1665,11 @@ def main():
# the removal of non-existing entries.
# Ensure certificates are present
if certificate is not None and len(certificate) > 0:
for _certificate in certificate:
certificate_add = gen_add_list(
certificate, res_find.get("usercertificate"))
if certificate_add is not None and \
len(certificate_add) > 0:
for _certificate in certificate_add:
commands.append([name, "user_add_cert",
{
"usercertificate":
@@ -1641,8 +1681,11 @@ def main():
# one reliably (https://pagure.io/freeipa/issue/8097)
# Ensure certmapdata are present
if certmapdata is not None and len(certmapdata) > 0:
for _data in certmapdata:
certmapdata_add = gen_add_list(
certmapdata, res_find.get("ipacertmapdata"))
if certmapdata_add is not None and \
len(certmapdata_add) > 0:
for _data in certmapdata_add:
commands.append([name, "user_add_certmapdata",
gen_certmapdata_args(_data)])
@@ -1663,10 +1706,12 @@ def main():
msg="No user '%s'" % name)
# Ensure managers are absent
if manager is not None and len(manager) > 0:
manager_del = gen_intersection_list(
manager, res_find.get("manager"))
if manager_del is not None and len(manager_del) > 0:
commands.append([name, "user_remove_manager",
{
"user": manager,
"user": manager_del,
}])
# Principals need to be added and removed one by one,
@@ -1675,10 +1720,12 @@ def main():
# the removal of non-existing entries.
# Ensure principals are absent
if principal is not None and len(principal) > 0:
principal_del = gen_intersection_list(
principal, res_find.get("krbprincipalname"))
if principal_del is not None and len(principal_del) > 0:
commands.append([name, "user_remove_principal",
{
"krbprincipalname": principal,
"krbprincipalname": principal_del,
}])
# Certificates need to be added and removed one by one,
@@ -1687,8 +1734,11 @@ def main():
# the removal of non-existing entries.
# Ensure certificates are absent
if certificate is not None and len(certificate) > 0:
for _certificate in certificate:
certificate_del = gen_intersection_list(
certificate, res_find.get("usercertificate"))
if certificate_del is not None and \
len(certificate_del) > 0:
for _certificate in certificate_del:
commands.append([name, "user_remove_cert",
{
"usercertificate":
@@ -1700,10 +1750,13 @@ def main():
# one reliably (https://pagure.io/freeipa/issue/8097)
# Ensure certmapdata are absent
if certmapdata is not None and len(certmapdata) > 0:
certmapdata_del = gen_intersection_list(
certmapdata, res_find.get("ipacertmapdata"))
if certmapdata_del is not None and \
len(certmapdata_del) > 0:
# Using issuer and subject can only be done one by
# one reliably (https://pagure.io/freeipa/issue/8097)
for _data in certmapdata:
for _data in certmapdata_del:
commands.append([name, "user_remove_certmapdata",
gen_certmapdata_args(_data)])
elif state == "undeleted":
@@ -1733,6 +1786,12 @@ def main():
else:
raise ValueError("No user '%s'" % name)
elif state == "renamed":
if res_find is None:
ansible_module.fail_json(msg="No user '%s'" % name)
else:
if rename != name:
commands.append([name, 'user_mod', {"rename": rename}])
else:
ansible_module.fail_json(msg="Unkown state '%s'" % state)
@@ -1741,7 +1800,7 @@ def main():
# Execute commands
changed = ansible_module.execute_ipa_commands(
commands, result_handler, exception_handler,
commands, result_handler, batch=True, keeponly=["randompassword"],
exit_args=exit_args, single_user=users is None)
# Done

View File

@@ -1,10 +1,10 @@
-r requirements-tests.txt
ipdb==0.13.4
pre-commit==2.20.0
flake8==6.0.0
flake8==7.0.0
flake8-bugbear
pylint==2.17.2
pylint>=3.2
wrapt==1.14.1
pydocstyle==6.3.0
yamllint==1.32.0
ansible-lint
yamllint==1.35.1
ansible-lint>=24.5.0

View File

@@ -201,6 +201,7 @@ Variable | Description | Required
`ipasssd_preserve_sssd` | The bool value defines if the old SSSD configuration will be preserved if it is not possible to merge it with a new one. `ipasssd_preserve_sssd` defaults to `no`. | no
`ipaclient_request_cert` | The bool value defines if the certificate for the machine wil be requested. The certificate will be stored in /etc/ipa/nssdb under the nickname "Local IPA host". . `ipaclient_request_cert` defaults to `no`. The option is deprecated and will be removed in a future release. | no
`ipaclient_keytab` | The string value contains the path on the node of a backup host keytab from a previous enrollment. | no
`ipaclient_automount_location` | Automount location | no
Server Variables

View File

@@ -152,8 +152,10 @@ def configure_dns_resolver(nameservers, searchdomains, fstore=None):
if not searchdomains or not isinstance(searchdomains, list):
raise AssertionError("searchdomains must be of type list")
changed = False
if fstore is not None and not fstore.has_file(paths.RESOLV_CONF):
fstore.backup_file(paths.RESOLV_CONF)
changed = True
resolve1_enabled = detect_resolve1_resolv_conf()
if "NetworkManager" not in services.knownservices:
@@ -192,6 +194,7 @@ def configure_dns_resolver(nameservers, searchdomains, fstore=None):
sdrd_service = services.service("systemd-resolved.service")
if sdrd_service.is_enabled():
sdrd_service.reload_or_restart()
changed = True
# Then configure NetworkManager or resolve.conf
if nm_service.is_enabled():
@@ -217,6 +220,7 @@ def configure_dns_resolver(nameservers, searchdomains, fstore=None):
outf.write(cfg)
# reload NetworkManager
nm_service.reload_or_restart()
changed = True
# Configure resolv.conf if NetworkManager and systemd-resoled are not
# enabled
@@ -231,6 +235,9 @@ def configure_dns_resolver(nameservers, searchdomains, fstore=None):
cfg.append("nameserver %s" % nameserver)
with open(paths.RESOLV_CONF, 'w') as outf:
outf.write('\n'.join(cfg))
changed = True
return changed
def unconfigure_dns_resolver(fstore=None):
@@ -239,8 +246,11 @@ def unconfigure_dns_resolver(fstore=None):
:param fstore: optional file store for resolv.conf restore
"""
changed = False
if fstore is not None and fstore.has_file(paths.RESOLV_CONF):
fstore.restore_file(paths.RESOLV_CONF)
changed = True
if os.path.isfile(NETWORK_MANAGER_IPA_CONF):
os.unlink(NETWORK_MANAGER_IPA_CONF)
@@ -252,6 +262,7 @@ def unconfigure_dns_resolver(fstore=None):
nm_service = services.knownservices['NetworkManager']
if nm_service.is_enabled():
nm_service.reload_or_restart()
changed = True
if os.path.isfile(SYSTEMD_RESOLVED_IPA_CONF):
os.unlink(SYSTEMD_RESOLVED_IPA_CONF)
@@ -261,6 +272,9 @@ def unconfigure_dns_resolver(fstore=None):
sdrd_service = services.service("systemd-resolved.service")
if sdrd_service.is_enabled():
sdrd_service.reload_or_restart()
changed = True
return changed
def main():
@@ -308,11 +322,12 @@ def main():
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
if state == "present":
configure_dns_resolver(nameservers, searchdomains, fstore)
changed = configure_dns_resolver(nameservers,
searchdomains, fstore)
else:
unconfigure_dns_resolver(fstore)
changed = unconfigure_dns_resolver(fstore)
module.exit_json(changed=True)
module.exit_json(changed=changed)
if __name__ == '__main__':

View File

@@ -89,9 +89,13 @@ try:
from ipapython.ipautil import run
from ipalib.constants import DEFAULT_CONFIG
try:
from ipalib.install.kinit import kinit_password, kinit_keytab
from ipalib.kinit import kinit_password, kinit_keytab
except ImportError:
from ipapython.ipautil import kinit_password, kinit_keytab
try:
from ipalib.install.kinit import kinit_password, kinit_keytab
except ImportError:
# pre 4.5.0
from ipapython.ipautil import kinit_password, kinit_keytab
except ImportError as _err:
MODULE_IMPORT_ERROR = str(_err)
else:

View File

@@ -68,7 +68,8 @@ RETURN = '''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_client import (
setup_logging, check_imports, options, configure_automount
setup_logging, check_imports, options, configure_automount, sysrestore,
paths, getargspec
)
@@ -94,10 +95,23 @@ def main():
options.automount_location = module.params.get('automount_location')
options.location = options.automount_location
changed = False
if options.automount_location:
configure_automount(options)
changed = True
argspec = getargspec(configure_automount)
if len(argspec.args) > 1:
fstore = sysrestore.FileStore(paths.IPA_CLIENT_SYSRESTORE)
statestore = sysrestore.StateFile(paths.IPA_CLIENT_SYSRESTORE)
module.exit_json(changed=True)
configure_automount(options, statestore)
# Reload the state as automount install may have modified it
fstore._load()
statestore._load()
else:
configure_automount(options)
module.exit_json(changed=changed)
if __name__ == '__main__':

View File

@@ -152,6 +152,10 @@ options:
The dist of nss_ldap or nss-pam-ldapd files if sssd is disabled
required: yes
type: dict
selinux_works:
description: True if selinux status check passed
required: false
type: bool
krb_name:
description: The krb5 config file name
type: str
@@ -189,7 +193,7 @@ from ansible.module_utils.ansible_ipa_client import (
CalledProcessError, tasks, client_dns, services,
update_ssh_keys, save_state, configure_ldap_conf, configure_nslcd_conf,
configure_openldap_conf, hardcode_ldap_server, getargspec, NUM_VERSION,
serialization
serialization, configure_selinux_for_client
)
@@ -224,6 +228,7 @@ def main():
no_dns_sshfp=dict(required=False, type='bool', default=False),
nosssd_files=dict(required=True, type='dict'),
krb_name=dict(required=True, type='str'),
selinux_works=dict(required=False, type='bool', default=False),
),
supports_check_mode=False,
)
@@ -274,6 +279,7 @@ def main():
options.sssd = not options.no_sssd
options.no_ac = False
nosssd_files = module.params.get('nosssd_files')
selinux_works = module.params.get('selinux_works')
krb_name = module.params.get('krb_name')
os.environ['KRB5_CONFIG'] = krb_name
@@ -474,6 +480,9 @@ def main():
logger.info("%s enabled", "SSSD" if options.sssd else "LDAP")
if options.sssd:
if selinux_works and configure_selinux_for_client is not None:
configure_selinux_for_client(statestore)
sssd = services.service('sssd', api)
try:
sssd.restart()

View File

@@ -226,6 +226,10 @@ nosssd_files:
returned: always
type: list
elements: str
selinux_works:
description: True if the selinux status check passed.
returned: always
type: bool
'''
import os
@@ -495,6 +499,8 @@ def main():
# not installer.no_krb5_offline_passwords
installer.sssd = not installer.no_sssd
selinux_works = False
try:
# client
@@ -529,7 +535,7 @@ def main():
"You must be root to run ipa-client-install.",
rval=CLIENT_INSTALL_ERROR)
tasks.check_selinux_status()
selinux_works = tasks.check_selinux_status()
# if is_ipa_client_installed(fstore, on_master=options.on_master):
# logger.error("IPA client is already configured on this system.")
@@ -971,7 +977,8 @@ def main():
ntp_pool=options.ntp_pool,
client_already_configured=client_already_configured,
ipa_python_version=IPA_PYTHON_VERSION,
nosssd_files=nosssd_files)
nosssd_files=nosssd_files,
selinux_works=selinux_works)
if __name__ == '__main__':

View File

@@ -46,7 +46,8 @@ __all__ = ["gssapi", "version", "ipadiscovery", "api", "errors", "x509",
"configure_nslcd_conf", "configure_ssh_config",
"configure_sshd_config", "configure_automount",
"configure_firefox", "sync_time", "check_ldap_conf",
"sssd_enable_ifp", "getargspec", "paths", "options",
"sssd_enable_ifp", "configure_selinux_for_client",
"getargspec", "paths", "options",
"IPA_PYTHON_VERSION", "NUM_VERSION", "certdb", "get_ca_cert",
"ipalib", "logger", "ipautil", "installer"]
@@ -172,9 +173,13 @@ try:
ipa_generate_password
from ipapython.dn import DN
try:
from ipalib.install.kinit import kinit_keytab, kinit_password
from ipalib.kinit import kinit_password, kinit_keytab
except ImportError:
from ipapython.ipautil import kinit_keytab, kinit_password
try:
from ipalib.install.kinit import kinit_keytab, kinit_password
except ImportError:
# pre 4.5.0
from ipapython.ipautil import kinit_keytab, kinit_password
from ipapython.ipa_log_manager import standard_logging_setup
from gssapi.exceptions import GSSError
try:
@@ -302,6 +307,11 @@ try:
except ImportError:
sssd_enable_ifp = None
try:
from ipaclient.install.client import configure_selinux_for_client
except ImportError:
configure_selinux_for_client = None
logger = logging.getLogger("ipa-client-install")
root_logger = logger

View File

@@ -166,18 +166,19 @@
register: result_ipaclient_get_otp
delegate_to: "{{ result_ipaclient_test.servers[0] }}"
- name: Install - Report error for OTP generation
ansible.builtin.debug:
msg: "{{ result_ipaclient_get_otp.msg }}"
when: result_ipaclient_get_otp is failed
failed_when: yes
- name: Install - Store the previously obtained OTP
no_log: yes
ansible.builtin.set_fact:
ipaadmin_orig_password: "{{ ipaadmin_password | default(omit) }}"
ipaadmin_password: "{{ result_ipaclient_get_otp.host.randompassword
if result_ipaclient_get_otp.host is defined }}"
rescue:
- name: Install - Report error for OTP generation
ansible.builtin.debug:
msg: "{{ result_ipaclient_get_otp.msg }}"
when: result_ipaclient_get_otp is failed
failed_when: yes
always:
- name: Install - Remove keytab temporary file
ansible.builtin.file:
@@ -383,6 +384,7 @@
| default(ipasssd_no_krb5_offline_passwords) }}"
no_dns_sshfp: "{{ ipaclient_no_dns_sshfp }}"
nosssd_files: "{{ result_ipaclient_test.nosssd_files }}"
selinux_works: "{{ result_ipaclient_test.selinux_works }}"
krb_name: "{{ result_ipaclient_temp_krb5.krb_name }}"
- name: Install - Configure SSH and SSHD
@@ -397,7 +399,7 @@
ipaclient_setup_automount:
servers: "{{ result_ipaclient_test.servers }}"
sssd: "{{ result_ipaclient_test.sssd }}"
automount_location: "{{ ipaautomount_location | default(omit) }}"
automount_location: "{{ ipaclient_automount_location | default(omit) }}"
- name: Install - Configure firefox
ipaclient_setup_firefox:

View File

@@ -139,7 +139,7 @@ def main():
conn.connect(ccache=installer._ccache)
remote_api.Command['hostgroup_add_member'](
u'ipaservers',
host=[unicode(api.env.host)],
host=[unicode(api.env.host)], # pylint: disable=W0012,E0606
)
finally:
if conn.isconnected():

View File

@@ -658,7 +658,7 @@ def main():
# Check authorization
result = remote_api.Command['hostgroup_find'](
cn=u'ipaservers',
host=[unicode(api.env.host)]
host=[unicode(api.env.host)] # pylint: disable=W0012,E0606
)['result']
add_to_ipaservers = not result

View File

@@ -104,7 +104,10 @@ try:
from ipaclient.install.ipachangeconf import IPAChangeConf
from ipalib.install import certstore, sysrestore
from ipapython.ipautil import ipa_generate_password
from ipalib.install.kinit import kinit_keytab
try:
from ipalib.kinit import kinit_keytab
except ImportError:
from ipalib.install.kinit import kinit_keytab
from ipapython import ipaldap, ipautil, kernel_keyring
from ipapython.certdb import IPA_CA_TRUST_FLAGS, \
EXTERNAL_CA_TRUST_FLAGS

View File

@@ -57,6 +57,11 @@
ipareplica_servers: "{{ groups['ipaservers'] | list }}"
when: groups.ipaservers is defined and ipareplica_servers is not defined
- name: Install - Set ipareplica_servers from cluster inventory
ansible.builtin.set_fact:
ipareplica_servers: "{{ groups['ipaserver'] | list }}"
when: ipareplica_servers is not defined and groups.ipaserver is defined
- name: Install - Set default principal if no keytab is given
ansible.builtin.set_fact:
ipaadmin_principal: admin

View File

@@ -77,9 +77,13 @@ try:
from ipapython.ipautil import run
from ipalib.constants import DEFAULT_CONFIG
try:
from ipalib.install.kinit import kinit_password
from ipalib.kinit import kinit_password
except ImportError:
from ipapython.ipautil import kinit_password
try:
from ipalib.install.kinit import kinit_password
except ImportError:
# pre 4.5.0
from ipapython.ipautil import kinit_password
except ImportError as _err:
MODULE_IMPORT_ERROR = str(_err)
else:

View File

@@ -226,7 +226,8 @@ from ansible.module_utils.ansible_ipa_server import (
redirect_stdout, adtrust, api, default_subject_base,
default_ca_subject_dn, ipautil, installutils, ca, kra, dns,
get_server_ip_address, no_matching_interface_for_ip_address_warning,
services, logger, tasks, update_hosts_file, ScriptError
services, logger, tasks, update_hosts_file, ScriptError, IPAChangeConf,
realm_to_ldapi_uri
)
@@ -365,6 +366,11 @@ def main():
fstore = sysrestore.FileStore(paths.SYSRESTORE)
sstore = sysrestore.StateFile(paths.SYSRESTORE)
domain_name = options.domain_name
realm_name = options.realm_name
host_name = options.host_name
setup_ca = options.setup_ca
# subject_base
if not options.subject_base:
options.subject_base = str(default_subject_base(options.realm_name))
@@ -391,27 +397,68 @@ def main():
# Create the management framework config file and finalize api
target_fname = paths.IPA_DEFAULT_CONF
# pylint: disable=invalid-name, consider-using-with
fd = open(target_fname, "w")
fd.write("[global]\n")
fd.write("host=%s\n" % options.host_name)
fd.write("basedn=%s\n" % ipautil.realm_to_suffix(options.realm_name))
fd.write("realm=%s\n" % options.realm_name)
fd.write("domain=%s\n" % options.domain_name)
fd.write("xmlrpc_uri=https://%s/ipa/xml\n" %
ipautil.format_netloc(options.host_name))
fd.write("ldap_uri=ldapi://%%2fvar%%2frun%%2fslapd-%s.socket\n" %
installutils.realm_to_serverid(options.realm_name))
if options.setup_ca:
fd.write("enable_ra=True\n")
fd.write("ra_plugin=dogtag\n")
fd.write("dogtag_version=10\n")
if realm_to_ldapi_uri is not None:
ipaconf = IPAChangeConf("IPA Server Install")
ipaconf.setOptionAssignment(" = ")
ipaconf.setSectionNameDelimiters(("[", "]"))
xmlrpc_uri = 'https://{0}/ipa/xml'.format(
ipautil.format_netloc(host_name))
ldapi_uri = realm_to_ldapi_uri(realm_name)
# [global] section
gopts = [
ipaconf.setOption('host', host_name),
ipaconf.setOption('basedn',
ipautil.realm_to_suffix(realm_name)),
ipaconf.setOption('realm', realm_name),
ipaconf.setOption('domain', domain_name),
ipaconf.setOption('xmlrpc_uri', xmlrpc_uri),
ipaconf.setOption('ldap_uri', ldapi_uri),
ipaconf.setOption('mode', 'production')
]
if setup_ca:
gopts.extend([
ipaconf.setOption('enable_ra', 'True'),
ipaconf.setOption('ra_plugin', 'dogtag'),
ipaconf.setOption('dogtag_version', '10')
])
else:
gopts.extend([
ipaconf.setOption('enable_ra', 'False'),
ipaconf.setOption('ra_plugin', 'None')
])
opts = [
ipaconf.setSection('global', gopts),
{'name': 'empty', 'type': 'empty'}
]
ipaconf.newConf(target_fname, opts)
else:
fd.write("enable_ra=False\n")
fd.write("ra_plugin=none\n")
fd.write("mode=production\n")
fd.close()
# pylint: enable=invalid-name, consider-using-with
# pylint: disable=invalid-name, consider-using-with
fd = open(target_fname, "w")
fd.write("[global]\n")
fd.write("host=%s\n" % options.host_name)
fd.write("basedn=%s\n" % ipautil.realm_to_suffix(
options.realm_name))
fd.write("realm=%s\n" % options.realm_name)
fd.write("domain=%s\n" % options.domain_name)
fd.write("xmlrpc_uri=https://%s/ipa/xml\n" %
ipautil.format_netloc(options.host_name))
fd.write("ldap_uri=ldapi://%%2fvar%%2frun%%2fslapd-%s.socket\n" %
installutils.realm_to_serverid(options.realm_name))
if options.setup_ca:
fd.write("enable_ra=True\n")
fd.write("ra_plugin=dogtag\n")
fd.write("dogtag_version=10\n")
else:
fd.write("enable_ra=False\n")
fd.write("ra_plugin=none\n")
fd.write("mode=production\n")
fd.close()
# pylint: enable=invalid-name, consider-using-with
# Must be readable for everyone
os.chmod(target_fname, 0o644)

View File

@@ -131,7 +131,8 @@ from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_server import (
check_imports,
MAX_DOMAIN_LEVEL, AnsibleModuleLog, options, sysrestore, paths,
api_Backend_ldap2, ds_init_info, redirect_stdout, setup_logging
api_Backend_ldap2, ds_init_info, redirect_stdout, setup_logging,
krbinstance, service
)
@@ -221,6 +222,16 @@ def main():
with redirect_stdout(ansible_log):
ds.change_admin_password(options.admin_password)
# Force KDC to refresh the cached value of ipaKrbAuthzData by restarting.
# ipaKrbAuthzData has to be set with "MS-PAC" to trigger PAC generation,
# which is required to handle S4U2Proxy with the Bronze-Bit fix.
# Not doing so would cause API malfunction for around a minute, which is
# long enough to cause the hereafter client installation to fail.
krb = krbinstance.KrbInstance(fstore)
krb.set_output(ansible_log)
service.print_msg("Restarting the KDC")
krb.restart()
# done ##########################################################
ansible_module.exit_json(changed=True)

View File

@@ -354,7 +354,7 @@ def main():
options.no_hbac_allow, options._dirsrv_pkcs12_info,
options.no_pkinit)
# setup CA ##############################################################
# setup custodia ########################################################
if hasattr(custodiainstance, "get_custodia_instance"):
if hasattr(custodiainstance.CustodiaModes, "FIRST_MASTER"):
@@ -362,9 +362,14 @@ def main():
else:
mode = custodiainstance.CustodiaModes.MASTER_PEER
custodia = custodiainstance.get_custodia_instance(options, mode)
custodia.set_output(ansible_log)
with redirect_stdout(ansible_log):
custodia.create_instance()
else:
custodia = custodiainstance.CustodiaInstance(options.host_name,
options.realm_name)
custodia.set_output(ansible_log)
with redirect_stdout(ansible_log):
custodia.create_instance()
# setup CA ##############################################################
if options.setup_ca:
if not options.external_cert_files and options.external_ca:

View File

@@ -1,118 +0,0 @@
# -*- coding: utf-8 -*-
# Authors:
# Thomas Woerner <twoerner@redhat.com>
#
# Based on ipa-client-install code
#
# Copyright (C) 2017-2022 Red Hat
# see file 'COPYING' for use and warranty information
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
from __future__ import (absolute_import, division, print_function)
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.0',
'supported_by': 'community',
'status': ['preview'],
}
DOCUMENTATION = '''
---
module: ipaserver_setup_custodia
short_description: Setup custodia
description: Setup custodia
options:
realm:
description: Kerberos realm name of the IPA deployment
type: str
required: yes
hostname:
description: Fully qualified name of this host
type: str
required: no
setup_ca:
description: Configure a dogtag CA
type: bool
default: no
required: no
author:
- Thomas Woerner (@t-woerner)
'''
EXAMPLES = '''
'''
RETURN = '''
'''
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.ansible_ipa_server import (
check_imports, setup_logging, AnsibleModuleLog, options,
api_Backend_ldap2,
custodiainstance, redirect_stdout
)
def main():
ansible_module = AnsibleModule(
argument_spec=dict(
# basic
realm=dict(required=True, type='str'),
hostname=dict(required=False, type='str'),
setup_ca=dict(required=False, type='bool', default=False),
),
)
ansible_module._ansible_debug = True
check_imports(ansible_module)
setup_logging()
ansible_log = AnsibleModuleLog(ansible_module)
# set values ############################################################
options.realm_name = ansible_module.params.get('realm')
options.host_name = ansible_module.params.get('hostname')
options.setup_ca = ansible_module.params.get('setup_ca')
options.promote = False
# init ##################################################################
api_Backend_ldap2(options.host_name, options.setup_ca, connect=True)
# setup custodia ########################################################
if hasattr(custodiainstance, "get_custodia_instance"):
if hasattr(custodiainstance.CustodiaModes, "FIRST_MASTER"):
mode = custodiainstance.CustodiaModes.FIRST_MASTER
else:
mode = custodiainstance.CustodiaModes.MASTER_PEER
custodia = custodiainstance.get_custodia_instance(options, mode)
else:
custodia = custodiainstance.CustodiaInstance(options.host_name,
options.realm_name)
custodia.set_output(ansible_log)
with redirect_stdout(ansible_log):
custodia.create_instance()
# done ##################################################################
ansible_module.exit_json(changed=True)
if __name__ == '__main__':
main()

View File

@@ -1171,7 +1171,7 @@ def main():
changed=False,
ipa_python_version=IPA_PYTHON_VERSION,
# basic
domain=options.domain_name,
domain=domain_name,
realm=realm_name,
hostname=host_name,
_hostname_overridden=bool(options.host_name),

View File

@@ -44,7 +44,7 @@ __all__ = ["IPAChangeConf", "certmonger", "sysrestore", "root_logger",
"check_available_memory", "getargspec", "get_min_idstart",
"paths", "api", "ipautil", "adtrust_imported", "NUM_VERSION",
"time_service", "kra_imported", "dsinstance", "IPA_PYTHON_VERSION",
"NUM_VERSION", "SerialNumber"]
"NUM_VERSION", "SerialNumber", "realm_to_ldapi_uri"]
import sys
import logging
@@ -121,6 +121,10 @@ try:
)
from ipapython.dnsutil import check_zone_overlap
from ipapython.dn import DN
try:
from ipapython.ipaldap import realm_to_ldapi_uri
except ImportError:
realm_to_ldapi_uri = None
try:
from ipaclient.install import timeconf
from ipaclient.install.client import sync_time

View File

@@ -267,12 +267,6 @@
idmax: "{{ result_ipaserver_test.idmax }}"
_pkinit_pkcs12_info: "{{ result_ipaserver_test._pkinit_pkcs12_info if result_ipaserver_test._pkinit_pkcs12_info != None else omit }}"
- name: Install - Setup custodia
ipaserver_setup_custodia:
realm: "{{ result_ipaserver_test.realm }}"
hostname: "{{ result_ipaserver_test.hostname }}"
setup_ca: "{{ result_ipaserver_test.setup_ca }}"
- name: Install - Setup CA
ipaserver_setup_ca:
dm_password: "{{ ipadm_password }}"

View File

@@ -21,7 +21,7 @@ parameters:
jobs:
- job: Test_Group${{ parameters.group_number }}
displayName: Run playbook tests ${{ parameters.scenario }} (${{ parameters.group_number }}/${{ parameters.number_of_groups }})
timeoutInMinutes: 240
timeoutInMinutes: 360
variables:
- template: variables.yaml
- template: variables_${{ parameters.scenario }}.yaml

View File

@@ -15,8 +15,9 @@
#
---
variables:
empty: true
# empty: true
# ipa_enabled_modules: >-
# ipa_enabled_tests: >-
# ipa_disabled_modules: >-
ipa_disabled_modules: >-
config
# ipa_disabled_tests: >-

View File

@@ -0,0 +1,7 @@
basicConstraints = CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
authorityKeyIdentifier = keyid,issuer
subjectAltName = @alt_names
[alt_names]
DNS.1 = ${ENV::HOST_FQDN}

View File

@@ -0,0 +1,19 @@
basicConstraints = CA:FALSE
keyUsage = nonRepudiation,digitalSignature,keyEncipherment,keyAgreement
extendedKeyUsage = 1.3.6.1.5.2.3.5
subjectKeyIdentifier = hash
authorityKeyIdentifier = keyid,issuer
issuerAltName = issuer:copy
subjectAltName = otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name
[kdc_princ_name]
realm = EXP:0,GeneralString:${ENV::REALM_NAME}
principal_name = EXP:1,SEQUENCE:kdc_principal_seq
[kdc_principal_seq]
name_type = EXP:0,INTEGER:1
name_string = EXP:1,SEQUENCE:kdc_principals
[kdc_principals]
princ1 = GeneralString:krbtgt
princ2 = GeneralString:${ENV::REALM_NAME}

View File

@@ -1,20 +0,0 @@
[kdc_cert]
basicConstraints=CA:FALSE
keyUsage=nonRepudiation,digitalSignature,keyEncipherment,keyAgreement
extendedKeyUsage=1.3.6.1.5.2.3.5
subjectKeyIdentifier=hash
authorityKeyIdentifier=keyid,issuer
issuerAltName=issuer:copy
subjectAltName=otherName:1.3.6.1.5.2.2;SEQUENCE:kdc_princ_name
[kdc_princ_name]
realm=EXP:0,GeneralString:${ENV::REALM}
principal_name=EXP:1,SEQUENCE:kdc_principal_seq
[kdc_principal_seq]
name_type=EXP:0,INTEGER:1
name_string=EXP:1,SEQUENCE:kdc_principals
[kdc_principals]
princ1=GeneralString:krbtgt
princ2=GeneralString:${ENV::REALM}

View File

@@ -7,9 +7,6 @@
- name: Run generate-certificates.sh
ansible.builtin.command: >
/bin/bash
generate-certificates.sh delete "{{ item }}"
generate-certificates.sh cleanup
args:
chdir: "{{ playbook_dir }}"
with_items:
- "{{ groups.ipaserver[0] }}"
- "{{ groups.ipareplicas[0] }}"

View File

@@ -1,153 +1,177 @@
#!/usr/bin/env bash
ROOT_CA_DIR="certificates/root-ca"
DIRSRV_CERTS_DIR="certificates/dirsrv"
HTTPD_CERTS_DIR="certificates/httpd"
PKINIT_CERTS_DIR="certificates/pkinit"
CERTIFICATES="certificates"
ROOT_CA_DIR="${CERTIFICATES}/root-ca"
DIRSRV_CERTS_DIR="${CERTIFICATES}/dirsrv"
HTTPD_CERTS_DIR="${CERTIFICATES}/httpd"
PKINIT_CERTS_DIR="${CERTIFICATES}/pkinit"
EXTENSIONS_CONF="${CERTIFICATES}/extensions.conf"
PKINIT_EXTENSIONS_CONF="${CERTIFICATES}/pkinit-extensions.conf"
PKCS12_PASSWORD="SomePKCS12password"
# generate_ipa_pkcs12_certificate \
# $cert_name $ipa_fqdn $certs_dir $root_ca_cert $root_ca_private_key extensions_file extensions_name
function generate_ipa_pkcs12_certificate {
# create_ca \
# $domain_name
function create_ca {
cert_name=$1
ipa_fqdn=$2
certs_dir=$3
root_ca_cert=$4
root_ca_private_key=$5
extensions_file=$6
extensions_name=$7
# Generate CSR and private key
openssl req -new -newkey rsa:4096 -nodes \
-subj "/C=US/ST=Test/L=Testing/O=Default/CN=${ipa_fqdn}" \
-keyout "${certs_dir}/private.key" \
-out "${certs_dir}/request.csr"
# Sign CSR to generate PEM certificate
if [ -z "${extensions_file}" ]; then
openssl x509 -req -days 365 -sha256 \
-CAcreateserial \
-CA "${root_ca_cert}" \
-CAkey "${root_ca_private_key}" \
-in "${certs_dir}/request.csr" \
-out "${certs_dir}/cert.pem"
else
openssl x509 -req -days 365 -sha256 \
-CAcreateserial \
-CA "${ROOT_CA_DIR}/cert.pem" \
-CAkey "${ROOT_CA_DIR}/private.key" \
-extfile "${extensions_file}" \
-extensions "${extensions_name}" \
-in "${certs_dir}/request.csr" \
-out "${certs_dir}/cert.pem"
fi
# Convert certificate to PKCS12 format
openssl pkcs12 -export \
-name "${cert_name}" \
-certfile "${root_ca_cert}" \
-in "${certs_dir}/cert.pem" \
-inkey "${certs_dir}/private.key" \
-passout "pass:${PKCS12_PASSWORD}" \
-out "${certs_dir}/cert.p12"
}
# generate_ipa_pkcs12_certificates $ipa_fqdn $ipa_domain
function generate_ipa_pkcs12_certificates {
host=$1
if [ -z "$host" ]; then
echo "ERROR: ipa-host-fqdn is not set"
echo
echo "usage: $0 create ipa-host-fqdn domain"
exit 0;
fi
domain=$2
if [ -z "$domain" ]; then
domain_name=$1
if [ -z "${domain_name}" ]; then
echo "ERROR: domain is not set"
echo
echo "usage: $0 create ipa-host-fqdn domain"
echo "usage: $0 ca <domain>"
exit 0;
fi
realm=${domain_name^^}
# Generate certificates folder structure
export REALM_NAME=${realm}
# Create certificates folder structure
mkdir -p "${ROOT_CA_DIR}"
mkdir -p "${DIRSRV_CERTS_DIR}/$host"
mkdir -p "${HTTPD_CERTS_DIR}/$host"
mkdir -p "${PKINIT_CERTS_DIR}/$host"
# Generate root CA
# Create root CA
if [ ! -f "${ROOT_CA_DIR}/private.key" ]; then
openssl genrsa \
-out "${ROOT_CA_DIR}/private.key" 4096
# create aes encrypted private key
openssl genrsa -out "${ROOT_CA_DIR}/private.key" 4096
openssl req -new -x509 -sha256 -nodes -days 3650 \
-subj "/C=US/ST=Test/L=Testing/O=Default" \
# create certificate, 1826 days = 5 years
openssl req -x509 -new -nodes -sha256 -days 1826 \
-subj "/C=US/ST=Test/L=Testing/O=Default/CN=Test Root CA" \
-key "${ROOT_CA_DIR}/private.key" \
-out "${ROOT_CA_DIR}/cert.pem"
fi
# Generate a certificate for the Directory Server
if [ ! -f "${DIRSRV_CERTS_DIR}/$host/cert.pem" ]; then
generate_ipa_pkcs12_certificate \
"dirsrv-cert" \
"$host" \
"${DIRSRV_CERTS_DIR}/$host" \
"${ROOT_CA_DIR}/cert.pem" \
"${ROOT_CA_DIR}/private.key"
fi
# Generate a certificate for the Apache server
if [ ! -f "${HTTPD_CERTS_DIR}/$host/cert.pem" ]; then
generate_ipa_pkcs12_certificate \
"httpd-cert" \
"$host" \
"${HTTPD_CERTS_DIR}/$host" \
"${ROOT_CA_DIR}/cert.pem" \
"${ROOT_CA_DIR}/private.key"
fi
# Generate a certificate for the KDC PKINIT
if [ ! -f "${PKINIT_CERTS_DIR}/$host/cert.pem" ]; then
export REALM=${domain^^}
generate_ipa_pkcs12_certificate \
"pkinit-cert" \
"$host" \
"${PKINIT_CERTS_DIR}/$host" \
"${ROOT_CA_DIR}/cert.pem" \
"${ROOT_CA_DIR}/private.key" \
"${PKINIT_CERTS_DIR}/extensions.conf" \
"kdc_cert"
fi
}
# delete_ipa_pkcs12_certificates $ipa_fqdn
function delete_ipa_pkcs12_certificates {
# create_host_pkcs12_certificate \
# $cert_name $certs_dir $root_ca_cert $extensions_file
function create_host_pkcs12_certificate {
host=$1
if [ -z "$host" ]; then
echo "ERROR: ipa-host-fqdn is not set"
cert_name=$1
certs_dir=$2
root_ca_cert=$3
extensions_file=$4
# Create CSR and private key
openssl req -new -nodes -newkey rsa:4096 \
-subj "/C=US/ST=Test/L=Testing/O=Default/CN=${cert_name}" \
-keyout "${certs_dir}/private.key" \
-out "${certs_dir}/request.csr"
# Sign CSR to create PEM certificate
openssl x509 -req -days 1460 -sha256 -CAcreateserial \
-CAkey "${ROOT_CA_DIR}/private.key" \
-CA "${root_ca_cert}" \
-in "${certs_dir}/request.csr" \
-out "${certs_dir}/cert.pem" \
-extfile "${extensions_file}"
# Convert certificate to PKCS12 format
openssl pkcs12 -export \
-name "${cert_name}" \
-certfile "${root_ca_cert}" \
-passout "pass:${PKCS12_PASSWORD}" \
-inkey "${certs_dir}/private.key" \
-in "${certs_dir}/cert.pem" \
-out "${certs_dir}/cert.p12"
}
# create_ipa_pkcs12_certificates \
# $host_fqdn $domain_name
function create_host_certificates {
host_fqdn=$1
if [ -z "${host_fqdn}" ]; then
echo "ERROR: host-fqdn is not set"
echo
echo "usage: $0 delete ipa-host-fqdn"
echo "usage: $0 create <host-fqdn> [<domain>]"
exit 0;
fi
rm -f certificates/*/"$host"/*
rm -f "${ROOT_CA_DIR}"/*
domain_name=$2
[ -z "${domain_name}" ] && domain_name=${host_fqdn#*.*}
if [ -z "${domain_name}" ]; then
echo "ERROR: domain is not set and can not be created from host fqdn"
echo
echo "usage: $0 create <host-fqdn> [<domain>]"
exit 0;
fi
realm=${domain_name^^}
export HOST_FQDN=${host_fqdn}
export REALM_NAME=${realm}
if [ ! -f "${ROOT_CA_DIR}/private.key" ]; then
create_ca "${domain_name}"
fi
# Create certificates folder structure
mkdir -p "${DIRSRV_CERTS_DIR}/${host_fqdn}"
mkdir -p "${HTTPD_CERTS_DIR}/${host_fqdn}"
mkdir -p "${PKINIT_CERTS_DIR}/${host_fqdn}"
# Create a certificate for the Directory Server
if [ ! -f "${DIRSRV_CERTS_DIR}/${host_fqdn}/cert.pem" ]; then
create_host_pkcs12_certificate \
"dirsrv-cert" \
"${DIRSRV_CERTS_DIR}/${host_fqdn}" \
"${ROOT_CA_DIR}/cert.pem" \
"${EXTENSIONS_CONF}"
fi
# Create a certificate for the Apache server
if [ ! -f "${HTTPD_CERTS_DIR}/${host_fqdn}/cert.pem" ]; then
create_host_pkcs12_certificate \
"httpd-cert" \
"${HTTPD_CERTS_DIR}/${host_fqdn}" \
"${ROOT_CA_DIR}/cert.pem" \
"${EXTENSIONS_CONF}"
fi
# Create a certificate for the KDC PKINIT
if [ ! -f "${PKINIT_CERTS_DIR}/${host_fqdn}/cert.pem" ]; then
create_host_pkcs12_certificate \
"pkinit-cert" \
"${PKINIT_CERTS_DIR}/${host_fqdn}" \
"${ROOT_CA_DIR}/cert.pem" \
"${PKINIT_EXTENSIONS_CONF}"
fi
}
# delete_host_certificates \
# $host_fqdn
function delete_host_certificates {
host_fqdn=$1
if [ -z "${host_fqdn}" ]; then
echo "ERROR: host-fqdn is not set"
echo
echo "usage: $0 delete <host-fqdn>"
exit 0;
fi
rm -rf certificates/*/"${host_fqdn}"/
}
# cleanup \
# $host_fqdn
function cleanup {
rm -rf certificates/*/
}
# Entrypoint
case "$1" in
ca)
create_ca "$2"
;;
create)
generate_ipa_pkcs12_certificates "$2" "$3"
create_host_certificates "$2" "$3"
;;
delete)
delete_ipa_pkcs12_certificates "$2"
delete_host_certificates "$2"
;;
cleanup)
cleanup
;;
*)
echo $"Usage: $0 {create|delete}"
echo $"Usage: $0 {create|delete|ca|cleanup}"
;;
esac

View File

@@ -30,7 +30,7 @@
check_mode: yes
register: sid_disabled
- name: Ensure netbios_name can't be changed without SID enabled. # noqa 503
- name: Ensure netbios_name can't be changed without SID enabled. # noqa no-handler
ipaconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
@@ -39,7 +39,7 @@
failed_when: not result.failed and "SID generation must be enabled" in result.msg
when: sid_disabled.changed
- name: Ensure SIDs can't be changed without SID enabled. # noqa 503
- name: Ensure SIDs can't be changed without SID enabled. # noqa no-handler
ipaconfig:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"

View File

@@ -0,0 +1,227 @@
---
- name: Test delegation
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: no
gather_facts: no
tasks:
- name: Test different cases for string case.
block:
# CLEANUP TEST ITEMS
- name: Ensure delegation "basic manager attributes" is absent
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
state: absent
# CREATE TEST ITEMS
- name: Ensure test group managers is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: managers
- name: Ensure test group employees is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: employees
# TESTS
- name: Ensure delegation "basic manager attributes" is present, group/membergroup mixed case
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- businesscategory
group: Managers
membergroup: Employees
register: result
failed_when: not result.changed or result.failed
- name: Ensure delegation "basic manager attributes" is present, group lowercase
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- businesscategory
group: "{{ 'Managers' | lower }}"
membergroup: Employees
register: result
failed_when: result.changed or result.failed
- name: Ensure delegation "basic manager attributes" is present, group uppercase
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- businesscategory
group: "{{ 'Managers' | upper }}"
membergroup: Employees
register: result
failed_when: result.changed or result.failed
- name: Ensure delegation "basic manager attributes" is present, permission upercase
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: "{{ 'read' | upper }}"
attribute:
- businesscategory
group: managers
membergroup: Employees
register: result
failed_when: result.changed or result.failed
- name: Ensure delegation "basic manager attributes" is present, permission mixed case
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: Read
attribute:
- businesscategory
group: managers
membergroup: Employees
register: result
failed_when: result.changed or result.failed
- name: Ensure delegation "basic manager attributes" is present, attribute upercase
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- "{{ 'businesscategory' | upper }}"
group: managers
membergroup: Employees
register: result
failed_when: result.changed or result.failed
- name: Ensure delegation "basic manager attributes" is present, attribute mixed case
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- BusinessCategory
group: managers
membergroup: Employees
register: result
failed_when: result.changed or result.failed
# membergroup does not use case insensitive comparison
- name: Ensure delegation "basic manager attributes" is present, membergroup lowercase
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- businesscategory
group: managers
membergroup: "{{ 'Employees' | lower }}"
register: result
failed_when: not result.changed or result.failed
- name: Ensure delegation "basic manager attributes" is present, membergroup uppercase
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- businesscategory
group: managers
membergroup: "{{ 'Employees' | upper }}"
register: result
failed_when: not result.changed or result.failed
- name: Ensure delegation "basic manager attributes" is present, group/membergroup mixed case
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- businesscategory
group: Managers
membergroup: Employees
register: result
failed_when: not result.changed or result.failed
# tests for action: member
- name: Ensure delegation "basic manager attributes" is present, with group and attribute in mixed case
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- BusinessCategory
group: Managers
membergroup: Employees
- name: Ensure delegation "basic manager attributes" is present, attribute mixed case
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
permission: read
attribute:
- BusinessCategory
group: managers
membergroup: employees
- name: Ensure delegation "basic manager attributes" member is present, attribute upercase
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
attribute:
- "{{ 'BusinessCategory' | upper }}"
action: member
register: result
failed_when: result.changed or result.failed
- name: Ensure delegation "basic manager attributes" member is present, attribute lowercase
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
attribute:
- "{{ 'BusinessCategory' | lower }}"
action: member
register: result
failed_when: result.changed or result.failed
always:
# CLEANUP TEST ITEMS
- name: Ensure delegation "basic manager attributes" is absent
ipadelegation:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: "basic manager attributes"
state: absent
- name: Ensure test groups are absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: managers,employees
state: absent

View File

@@ -1549,7 +1549,7 @@
- name: Cleanup test environment.
ansible.builtin.include_tasks: env_cleanup.yml
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1]
become: no

View File

@@ -3,6 +3,10 @@
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: true
gather_facts: true
module_defaults:
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
tasks:
@@ -13,8 +17,6 @@
# Tests
- name: Check if zone is present, when it shouldn't be.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: present
check_mode: yes
@@ -23,8 +25,6 @@
- name: Check if zone is present again, when it shouldn't be.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: present
check_mode: yes
@@ -33,8 +33,6 @@
- name: Ensure zone is present.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: present
register: result
@@ -42,8 +40,6 @@
- name: Check if zone is present, when it should be.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: present
check_mode: yes
@@ -52,8 +48,6 @@
- name: Ensure zone is present, again.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: present
register: result
@@ -61,8 +55,6 @@
- name: Ensure zone is disabled.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: disabled
register: result
@@ -70,8 +62,6 @@
- name: Ensure zone is disabled, again.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: disabled
register: result
@@ -79,8 +69,6 @@
- name: Ensure zone is enabled.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: enabled
register: result
@@ -88,8 +76,6 @@
- name: Ensure zone is enabled, again.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
state: enabled
register: result
@@ -97,8 +83,6 @@
- name: Ensure forward_policy is none.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forward_policy: none
register: result
@@ -106,8 +90,6 @@
- name: Ensure forward_policy is none, again.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forward_policy: none
register: result
@@ -115,8 +97,6 @@
- name: Ensure forward_policy is first.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forward_policy: first
register: result
@@ -124,8 +104,6 @@
- name: Ensure forward_policy is first, again.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forward_policy: first
register: result
@@ -133,8 +111,6 @@
- name: Ensure first forwarder is set.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forwarders:
- ip_address: 8.8.8.8
@@ -144,8 +120,6 @@
- name: Ensure first and second forwarder are set.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forwarders:
- ip_address: 8.8.8.8
@@ -156,8 +130,6 @@
- name: Ensure first and second forwarder are set, again.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forwarders:
- ip_address: 8.8.8.8
@@ -168,8 +140,6 @@
- name: Ensure only second forwarder is set.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forwarders:
- ip_address: 2001:4860:4860::8888
@@ -178,16 +148,12 @@
- name: Nothing changes.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
register: result
failed_when: result.changed or result.failed
- name: Ensure no forwarders are set.
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testzone.local
forwarders: []
register: result
@@ -195,56 +161,70 @@
- name: Create zones test1
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: test1.testzone.local
register: result
failed_when: not result.changed or result.failed
- name: Create zones test1, again
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: test1.testzone.local
register: result
failed_when: result.changed or result.failed
- name: Create zones test2
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: test2.testzone.local
register: result
failed_when: not result.changed or result.failed
- name: Create zones test2, again
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: test2.testzone.local
register: result
failed_when: result.changed or result.failed
- name: Create zones test3
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: test3.testzone.local
register: result
failed_when: not result.changed or result.failed
- name: Create zones test3, again
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: test3.testzone.local
register: result
failed_when: result.changed or result.failed
- name: Ensure zone test1.testzone.local has management permissioon
ipadnszone:
name: test1.testzone.local
permission: true
register: result
failed_when: not result.changed or result.failed
- name: Ensure zone test1.testzone.local has management permissioon
ipadnszone:
name: test1.testzone.local
permission: true
register: result
failed_when: result.changed or result.failed
- name: Ensure zone test1.testzone.local don't have management permissioon
ipadnszone:
name: test1.testzone.local
permission: false
register: result
failed_when: not result.changed or result.failed
- name: Ensure zone test1.testzone.local don't have management permissioon
ipadnszone:
name: test1.testzone.local
permission: false
register: result
failed_when: result.changed or result.failed
- name: Ensure multiple zones are absent
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name:
- test1.testzone.local
- test2.testzone.local
@@ -255,8 +235,6 @@
- name: Ensure multiple zones are absent, again
ipadnszone:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name:
- test1.testzone.local
- test2.testzone.local

View File

@@ -3,6 +3,13 @@
hosts: "{{ ipa_test_host | default('ipaserver') }}"
become: true
gather_facts: true
module_defaults:
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
tasks:
# setup
@@ -19,24 +26,18 @@
- name: Ensure users user1, user2 and user3 are absent
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: user1,user2,user3
state: absent
- name: Ensure group group3, group2 and group1 are absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group3,group2,group1
name: groupren,group3,group2,group1
state: absent
# CREATE TEST ITEMS
- name: Ensure users user1..user3 are present
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
users:
- name: user1
first: user1
@@ -54,56 +55,74 @@
- name: Ensure group1 is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
register: result
failed_when: not result.changed or result.failed
- name: Ensure group1 is present again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
register: result
failed_when: result.changed or result.failed
- name: Rename group1 to groupren
ipagroup:
name: group1
rename: groupren
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: Rename group1 to groupren
ipagroup:
name: group1
rename: groupren
state: renamed
register: result
failed_when: not result.failed or "No group 'group1'" not in result.msg
- name: Rename group groupren to groupren
ipagroup:
name: groupren
rename: groupren
state: renamed
register: result
failed_when: result.changed or result.failed
- name: Rename group groupren back to group1
ipagroup:
name: groupren
rename: group1
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: Ensure group2 is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group2
register: result
failed_when: not result.changed or result.failed
- name: Ensure group2 is present again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group2
register: result
failed_when: result.changed or result.failed
- name: Ensure group3 is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group3
register: result
failed_when: not result.changed or result.failed
- name: Ensure group3 is present again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group3
register: result
failed_when: result.changed or result.failed
- name: Ensure groups group2 and group3 are present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
group:
- group2
@@ -114,8 +133,6 @@
- name: Ensure groups group2 and group3 are present in group group1 again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
group:
- group2
@@ -126,8 +143,6 @@
- name: Ensure group3 ia present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
group:
- group3
@@ -143,8 +158,6 @@
- name: Ensure service "{{ 'HTTP/' + fqdn_at_domain }}" is present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'HTTP/' + fqdn_at_domain }}"
@@ -154,8 +167,6 @@
- name: Ensure service "{{ 'HTTP/' + fqdn_at_domain }}" is present in group group1, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'HTTP/' + fqdn_at_domain }}"
@@ -165,8 +176,6 @@
- name: Ensure service "{{ 'ldap/' + fqdn_at_domain }}" is present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'ldap/' + fqdn_at_domain }}"
@@ -176,8 +185,6 @@
- name: Ensure service "{{ 'ldap/' + fqdn_at_domain }}" is present in group group1, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'ldap/' + fqdn_at_domain }}"
@@ -187,8 +194,6 @@
- name: Ensure service "{{ 'HTTP/' + fqdn_at_domain }}" is absent in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'HTTP/' + fqdn_at_domain }}"
@@ -199,8 +204,6 @@
- name: Ensure service "{{ 'HTTP/' + fqdn_at_domain }}" is absent in group group1, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'HTTP/' + fqdn_at_domain }}"
@@ -211,8 +214,6 @@
- name: Ensure service "{{ 'ldap/' + fqdn_at_domain }}" is absent in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'ldap/' + fqdn_at_domain }}"
@@ -223,8 +224,6 @@
- name: Ensure service "{{ 'ldap/' + fqdn_at_domain }}" is absent in group group1, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'ldap/' + fqdn_at_domain }}"
@@ -235,8 +234,6 @@
- name: Ensure services are present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'HTTP/' + fqdn_at_domain }}"
@@ -247,8 +244,6 @@
- name: Ensure services are present in group group1, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'http/' + fqdn_at_domain }}"
@@ -259,8 +254,6 @@
- name: Ensure services are absent in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'HTTP/' + fqdn_at_domain }}"
@@ -272,8 +265,6 @@
- name: Ensure services are absent in group group1, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
service:
- "{{ 'HTTP/' + fqdn_at_domain }}"
@@ -283,12 +274,10 @@
register: result
failed_when: result.changed or result.failed
# user
# user
- name: Ensure users user1, user2 and user3 are present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -300,8 +289,6 @@
- name: Ensure users user1, user2 and user3 are present in group group1 again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -312,8 +299,6 @@
failed_when: result.changed or result.failed
#- ipagroup:
# ipaadmin_password: SomeADMINpassword
# ipaapi_context: "{{ ipa_context | default(omit) }}"
# name: group1
# user:
# - user7
@@ -321,8 +306,6 @@
- name: Ensure user user7 is absent in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user7
@@ -333,8 +316,6 @@
- name: Ensure group group4 is absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group4
state: absent
register: result
@@ -342,8 +323,6 @@
- name: Ensure groups group3, group2, and group1 are absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group3,group2,group1
state: absent
register: result
@@ -351,16 +330,12 @@
- name: Ensure group group1 is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
register: result
failed_when: not result.changed or result.failed
- name: Ensure users user1, user2 are present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -371,8 +346,6 @@
- name: Ensure users user1, user2 and user3 are present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -384,8 +357,6 @@
- name: Ensure users user1, user2 are present in group group1, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -396,8 +367,6 @@
- name: Ensure users user1, user2 and user3 are present in group group1, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -409,8 +378,6 @@
- name: Ensure group group1 is absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
state: absent
register: result
@@ -418,8 +385,6 @@
- name: Ensure group group1 with users user1, user2 is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -429,8 +394,6 @@
- name: Ensure group group1 with users user1, user2 and user3 is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -441,8 +404,6 @@
- name: Ensure group group1 with users user1, user2 and user3 is present, again
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -454,8 +415,6 @@
- name: Ensure only users user1, user2 are present in group group1
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group1
user:
- user1
@@ -467,8 +426,6 @@
- name: Ensure group group3, group2 and group1 are absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group3,group2,group1
state: absent
register: result
@@ -476,8 +433,6 @@
- name: Ensure users user1, user2 and user3 are absent
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: user1,user2,user3
state: absent
register: result

View File

@@ -0,0 +1,250 @@
---
- name: Test group members case insensitive
hosts: ipaserver
become: true
gather_facts: false
module_defaults:
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
vars:
test_users:
- { name: useR1, first: user1, last: last }
- { name: User2, first: user2, last: last }
user_names: "{{ test_users | map(attribute='name') }}"
test_groups:
- name: Group1
- name: Group2
group_names: "{{ test_groups | map(attribute='name') }}"
tasks:
- name: Include tasks ../env_freeipa_facts.yml
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
- name: Test in all supported versions of IPA
block:
# setup environment
- name: Ensure testgroup is absent
ipagroup:
name: testgroup
state: absent
- name: Ensure test users are present
ipauser:
users: "{{ test_users }}"
- name: Ensure test groups are present
ipagroup:
groups: "{{ test_groups }}"
# tests
- name: Test group presence with user members
vars:
test_cases:
- { id: 1, value: "{{ user_names[0] | lower }}", expected: true }
- { id: 2, value: "{{ user_names[0] | upper }}", expected: false }
- { id: 3, value: "{{ user_names[0] }}", expected: false }
- { id: 4, value: "{{ user_names }}", expected: true }
- { id: 5, value: "{{ user_names | upper }}", expected: false }
- { id: 6, value: "{{ user_names | lower }}", expected: false }
- { id: 7, value: "{{ user_names[1] }}", expected: true }
- { id: 8, value: "{{ user_names[1] | upper }}", expected: false }
- { id: 9, value: "{{ user_names[1] | lower }}", expected: false }
- { id: 10, value: [], expected: true }
block:
- name: Ensure group with user parameter present
ipagroup:
name: testgroup
user: "{{ item.value }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
- name: Test group presence with group parameter
vars:
test_cases:
- { id: 1, value: "{{ group_names[0] | lower }}", expected: true }
- { id: 2, value: "{{ group_names[0] | upper }}", expected: false }
- { id: 3, value: "{{ group_names[0] }}", expected: false }
- { id: 4, value: "{{ group_names }}", expected: true }
- { id: 5, value: "{{ group_names | upper }}", expected: false }
- { id: 6, value: "{{ group_names | lower }}", expected: false }
- { id: 7, value: "{{ group_names[1] }}", expected: true }
- { id: 8, value: "{{ group_names[1] | upper }}", expected: false }
- { id: 9, value: "{{ group_names[1] | lower }}", expected: false }
- { id: 10, value: [], expected: true }
block:
- name: Ensure group with group present
ipagroup:
name: testgroup
group: "{{ item.value }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
- name: Test group with group and user parameters, action member
vars:
test_cases:
- { id: 1, user: "{{ user_names }}", group: "{{ group_names }}", expected: true }
- { id: 2, user: "{{ user_names[0] }}", state: "absent", expected: true }
- { id: 3, user: "{{ user_names[1] }}", state: "present", expected: false }
- { id: 4, user: "{{ user_names[1] | upper }}", state: "present", expected: false }
- { id: 5, user: "{{ user_names[1] | lower }}", state: "present", expected: false }
- { id: 6, group: "{{ group_names[0] | upper }}", state: "present", expected: false }
- { id: 7, group: "{{ group_names[0] | lower }}", state: "present", expected: false }
- { id: 8, group: "{{ group_names[0] }}", state: "present", expected: false }
- { id: 9, user: "{{ user_names[0] }}", group: "{{ group_names[0] }}", state: "absent", expected: true }
- { id: 10, user: "{{ user_names[0] | lower }}", group: "{{ group_names[0] | upper }}", state: "absent", expected: false }
- { id: 11, user: "{{ user_names[0] | upper }}", group: "{{ group_names[0] | lower }}", state: "absent", expected: false }
- { id: 12, user: "{{ user_names[0] }}", group: "{{ group_names[0] }}", state: "absent", expected: false }
- { id: 13, group: "{{ group_names[0] | upper }}", state: "present", expected: true }
- { id: 14, group: "{{ group_names[0] | lower }}", state: "present", expected: false }
- { id: 15, group: "{{ group_names[0] }}", state: "present", expected: false }
- { id: 16, group: "{{ group_names[1] | upper }}", state: "present", expected: false }
- { id: 17, group: "{{ group_names[1] | lower }}", state: "present", expected: false }
- { id: 18, group: "{{ group_names[1] }}", state: "present", expected: false }
- { id: 19, user: "{{ user_names[1] | upper }}", state: "present", expected: false }
- { id: 20, user: "{{ user_names[1] | lower }}", state: "present", expected: false }
- { id: 21, user: "{{ user_names[1] }}", state: "present", expected: false }
block:
- name: Ensure group works with group/user attributes and action member
ipagroup:
name: testgroup
user: "{{ item.user | default(omit) }}"
group: "{{ item.group | default(omit) }}"
action: member
state: "{{ item.state | default('present') }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
always:
# cleanup
- name: "Ensure test groups test_groups are absent"
ipagroup:
name: "{{ group_names + ['testgroup'] }}"
state: absent
- name: "Ensure test users test_users are absent"
ipauser:
users: "{{ test_users }}"
state: absent
- name: Test in all IPA versions 8.4.4+
when: ipa_version is version('4.8.4', '>=')
block:
# setup environment
- name: Ensure test users are present
ipauser:
users: "{{ test_users }}"
- name: Ensure test groups are present
ipagroup:
groups: "{{ test_groups }}"
# tests
- name: Test group presence with memembermanager_user members
vars:
test_cases:
- { id: 1, value: "{{ user_names[0] | lower }}", expected: true }
- { id: 2, value: "{{ user_names[0] | upper }}", expected: false }
- { id: 3, value: "{{ user_names[0] }}", expected: false }
- { id: 4, value: "{{ user_names }}", expected: true }
- { id: 5, value: "{{ user_names | upper }}", expected: false }
- { id: 6, value: "{{ user_names | lower }}", expected: false }
- { id: 7, value: "{{ user_names[1] }}", expected: true }
- { id: 8, value: "{{ user_names[1] | upper }}", expected: false }
- { id: 9, value: "{{ user_names[1] | lower }}", expected: false }
- { id: 10, value: [], expected: true }
block:
- name: Ensure group with membermanager_user parameter present
ipagroup:
name: testgroup
membermanager_user: "{{ item.value }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
- name: Test group presence with membermanager_group parameter
vars:
test_cases:
- { id: 1, value: "{{ group_names[0] | lower }}", expected: true }
- { id: 2, value: "{{ group_names[0] | upper }}", expected: false }
- { id: 3, value: "{{ group_names[0] }}", expected: false }
- { id: 4, value: "{{ group_names }}", expected: true }
- { id: 5, value: "{{ group_names | upper }}", expected: false }
- { id: 6, value: "{{ group_names | lower }}", expected: false }
- { id: 7, value: "{{ group_names[1] }}", expected: true }
- { id: 8, value: "{{ group_names[1] | upper }}", expected: false }
- { id: 9, value: "{{ group_names[1] | lower }}", expected: false }
- { id: 10, value: [], expected: true }
block:
- name: Ensure group with membermanager_group present
ipagroup:
name: testgroup
membermanager_group: "{{ item.value }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
- name: Test group with membermanager_group and membermanager_user parameters, action member
vars:
test_cases:
- { id: 1, user: "{{ user_names }}", group: "{{ group_names }}", expected: true }
- { id: 2, user: "{{ user_names[0] }}", state: "absent", expected: true }
- { id: 3, user: "{{ user_names[1] }}", state: "present", expected: false }
- { id: 4, user: "{{ user_names[1] | upper }}", state: "present", expected: false }
- { id: 5, user: "{{ user_names[1] | lower }}", state: "present", expected: false }
- { id: 6, group: "{{ group_names[0] | upper }}", state: "present", expected: false }
- { id: 7, group: "{{ group_names[0] | lower }}", state: "present", expected: false }
- { id: 8, group: "{{ group_names[0] }}", state: "present", expected: false }
- { id: 9, user: "{{ user_names[0] }}", group: "{{ group_names[0] }}", state: "absent", expected: true }
- { id: 10, user: "{{ user_names[0] | lower }}", group: "{{ group_names[0] | upper }}", state: "absent", expected: false }
- { id: 11, user: "{{ user_names[0] | upper }}", group: "{{ group_names[0] | lower }}", state: "absent", expected: false }
- { id: 12, user: "{{ user_names[0] }}", group: "{{ group_names[0] }}", state: "absent", expected: false }
- { id: 13, group: "{{ group_names[0] | upper }}", state: "present", expected: true }
- { id: 14, group: "{{ group_names[0] | lower }}", state: "present", expected: false }
- { id: 15, group: "{{ group_names[0] }}", state: "present", expected: false }
- { id: 16, group: "{{ group_names[1] | upper }}", state: "present", expected: false }
- { id: 17, group: "{{ group_names[1] | lower }}", state: "present", expected: false }
- { id: 18, group: "{{ group_names[1] }}", state: "present", expected: false }
- { id: 19, user: "{{ user_names[1] | upper }}", state: "present", expected: false }
- { id: 20, user: "{{ user_names[1] | lower }}", state: "present", expected: false }
- { id: 21, user: "{{ user_names[1] }}", state: "present", expected: false }
block:
- name: Ensure group works with group/user attributes and action member
ipagroup:
name: testgroup
membermanager_user: "{{ item.user | default(omit) }}"
membermanager_group: "{{ item.group | default(omit) }}"
action: member
state: "{{ item.state | default('present') }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
always:
# cleanup
- name: "Ensure test groups test_groups are absent"
ipagroup:
name: "{{ group_names + ['testgroup'] }}"
state: absent
- name: "Ensure test users test_users are absent"
ipauser:
name: "{{ user_names }}"
state: absent

View File

@@ -19,7 +19,7 @@
- name: Remove test groups
ipagroup:
ipaadmin_password: SomeADMINpassword
name: group1,group2,group3,group4,group5,group6,group7,group8,group9,group10
name: group1,group2,group3,group4,group5,group6,group7,group8,group9,group10,newgroup1,newgroup2
state: absent
- name: Remove test users
@@ -130,10 +130,53 @@
register: result
failed_when: result.changed or not result.failed or "Only one group can be added at a time using 'name'." not in result.msg
- name: Ensure group1 and group2 exist
ipagroup:
ipaadmin_password: SomeADMINpassword
groups:
- name: group1
- name: group2
- name: Rename group1 and group2 to newgroup1 and newgroup2, respectively
ipagroup:
ipaadmin_password: SomeADMINpassword
groups:
- name: group1
rename: newgroup1
- name: group2
rename: newgroup2
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: Rename newgroup1 and newgroup2 to the same name
ipagroup:
ipaadmin_password: SomeADMINpassword
groups:
- name: newgroup1
rename: newgroup1
- name: newgroup2
rename: newgroup2
state: renamed
register: result
failed_when: result.changed or result.failed
- name: Rename newgroup1 and newgroup2 back to group1 and group2, respectively
ipagroup:
ipaadmin_password: SomeADMINpassword
groups:
- name: newgroup1
rename: group1
- name: newgroup2
rename: group2
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: Remove test groups
ipagroup:
ipaadmin_password: SomeADMINpassword
name: group1,group2,group3,group4,group5,group6,group7,group8,group9,group10
name: group1,group2,group3,group4,group5,group6,group7,group8,group9,group10,newgroup1,newgroup2
state: absent
- name: Remove test users

View File

@@ -9,7 +9,7 @@
tasks:
- name: Include groups.json
ansible.builtin.include_vars:
file: groups.json # noqa 505
file: groups.json
- name: Initialize groups_names
ansible.builtin.set_fact:

View File

@@ -9,7 +9,7 @@
tasks:
- name: Include groups.json
ansible.builtin.include_vars:
file: groups.json # noqa 505
file: groups.json
- name: Groups present len:{{ group_list | length }}
ipagroup:

View File

@@ -11,7 +11,7 @@
tasks:
- name: Include groups.json
ansible.builtin.include_vars:
file: groups.json # noqa 505
file: groups.json
- name: Size of groups slice.
ansible.builtin.debug:

View File

@@ -468,11 +468,51 @@
register: result
failed_when: result.changed or result.failed
# Specifically test 'Sudo', as FreeIPA adds a "Sudo" hbacsvcgroup instead of "sudo"
- name: Ensure 'sudo' works as hbacsvcgroup.
ipahbacrule:
ipaadmin_password: SomeADMINpassword
name: "test_sudo"
hbacsvcgroup:
- sudo
register: result
failed_when: not result.changed or result.failed
- name: Ensure 'sudo' works as hbacsvcgroup, again.
ipahbacrule:
ipaadmin_password: SomeADMINpassword
name: "test_sudo"
hbacsvcgroup:
- sudo
register: result
failed_when: result.changed or result.failed
- name: Ensure 'sudo' works as hbacsvcgroup, action member.
ipahbacrule:
ipaadmin_password: SomeADMINpassword
name: "test_sudo"
hbacsvcgroup:
- sudo
action: member
register: result
failed_when: result.changed or result.failed
- name: Ensure 'Sudo' works as hbacsvcgroup, action member.
ipahbacrule:
ipaadmin_password: SomeADMINpassword
name: "test_sudo"
hbacsvcgroup:
- Sudo
register: result
failed_when: result.changed or result.failed
always:
- name: Ensure test hbacrule is absent
ipahbacrule:
ipaadmin_password: SomeADMINpassword
name: testrule
name:
- testrule
- test_sudo
state: absent
- name: Ensure test users are absent

View File

@@ -99,7 +99,7 @@
register: result
failed_when: result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2, 3]

View File

@@ -98,7 +98,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2, 3]

View File

@@ -13,6 +13,7 @@
ansible.builtin.set_fact:
host1_fqdn: "{{ 'host1.' + ipaserver_domain }}"
host2_fqdn: "{{ 'host2.' + ipaserver_domain }}"
server_fqdn: "{{ ansible_facts['fqdn'] }}"
- name: Test hosts absent
ipahost:
@@ -86,10 +87,8 @@
- name: Assert randompassword is defined for host1 and host2.
ansible.builtin.assert:
that:
- ipahost.host["{{ host1_fqdn }}"].randompassword is
defined
- ipahost.host["{{ host2_fqdn }}"].randompassword is
defined
- ipahost.host[host1_fqdn].randompassword is defined
- ipahost.host[host2_fqdn].randompassword is defined
- name: Print generated random password for "{{ host1_fqdn }}"
ansible.builtin.debug:
@@ -99,11 +98,11 @@
ansible.builtin.debug:
var: ipahost.host["{{ host2_fqdn }}"].randompassword
- name: Enrolled host "{{ ansible_facts['fqdn'] }}" fails to set random password with update_password always
- name: Enrolled host "{{ server_fqdn }}" fails to set random password with update_password always
ipahost:
ipaadmin_password: SomeADMINpassword
hosts:
- name: "{{ ansible_facts['fqdn'] }}"
- name: "{{ server_fqdn }}"
random: yes
update_password: always
register: ipahost
@@ -112,8 +111,7 @@
- name: Assert randompassword is not defined for 'ansible_fqdn'.
ansible.builtin.assert:
that:
- ipahost.host["{{ ansible_facts['fqdn'] }}"].randompassword is
not defined
- ipahost.host[server_fqdn].randompassword is not defined
- "'Password cannot be set on enrolled host' in ipahost.msg"
- name: Hosts "{{ host1_fqdn }}" and "{{ host2_fqdn }}" absent

View File

@@ -0,0 +1,144 @@
---
- name: Test hostgroup members case insensitive
hosts: ipaserver
become: true
gather_facts: false
module_defaults:
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
ipahost:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
vars:
# Hostnames are supposed to have first letter
# capitalized for this test.
test_hosts:
- Host1
- Host2
test_hostgroups:
- testhostgroup1
# TestHostgrop2 is meant to use CamelCase here.
- TestHostGroup2
tasks:
- name: Test in all supported versions of IPA
block:
# setup environment
- name: Ensure domain name is set
ansible.builtin.set_fact:
ipa_domain: "test.local"
when: ipa_domain is not defined
- name: Ensure hostgroup testhostgroup1 and testhostgroup2 are absent
ipahostgroup:
name: "{{ test_hostgroups }}"
state: absent
- name: Ensure test hosts are present
ipahost:
name: "{{ item }}.{{ ipa_domain }}"
force: true
loop: "{{ test_hosts }}"
- name: Ensure hostgroup testhostgroup2 is present
ipahostgroup:
name: testhostgroup2
# tests
- name: Hostgroup should not be renamed only due to case
ipahostgroup:
name: testhostgroup2
rename: testhostgroup2
state: renamed
register: result
failed_when: result.changed or result.failed
- name: Test hostgroup presence with single host and action hostgroup
vars:
test_cases:
- { id: 1, value: "{{ test_hosts[0] | lower }}", expected: true }
- { id: 2, value: "{{ test_hosts[0] | upper }}", expected: false }
- { id: 3, value: "{{ test_hosts[0] }}", expected: false }
block:
- name: "Ensure hostgroup testhostgroup with host 'host1'"
ipahostgroup:
name: testhostgroup1
host: "{{ item.value }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
- name: Test hostgroup presence with multiple hosts and action hostgroup
vars:
test_cases:
- { id: 1, value: "{{ test_hosts | lower }}", expected: true }
- { id: 2, value: "{{ test_hosts | upper }}", expected: false }
- { id: 3, value: "{{ test_hosts }}", expected: false }
- { id: 4, value: "{{ test_hosts[1] }}", expected: true }
- { id: 5, value: "{{ test_hosts[1] | lower }}", expected: false }
- { id: 6, value: "{{ test_hosts[1] | upper }}", expected: false }
- { id: 7, value: "{{ test_hosts[0] }}", expected: true }
- { id: 8, value: "{{ test_hosts[0] | lower }}", expected: false }
- { id: 9, value: "{{ test_hosts[0] | upper }}", expected: false }
block:
- name: "Ensure hostgroup testhostgroup with host 'host1'"
ipahostgroup:
name: testhostgroup1
host: "{{ item.value }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
- name: Test hostgroup with multiple hosts and action member
vars:
test_cases:
- { id: 1, value: "{{ test_hosts | lower }}", state: "absent", expected: true }
- { id: 2, value: "{{ test_hosts | upper }}", state: "absent", expected: false }
- { id: 3, value: "{{ test_hosts }}", state: "present", expected: true }
- { id: 4, value: "{{ test_hosts[1] }}", state: "absent", expected: true }
- { id: 5, value: "{{ test_hosts[1] | lower }}", state: "absent", expected: false }
- { id: 6, value: "{{ test_hosts[1] | upper }}", state: "absent", expected: false }
- { id: 7, value: "{{ test_hosts[0] | lower }}", state: "present", expected: false }
- { id: 8, value: "{{ test_hosts[0] }}", state: "present", expected: false }
- { id: 9, value: "{{ test_hosts[0] | upper }}", state: "present", expected: false }
- { id: 10, value: "{{ test_hosts | upper }}", state: "present", expected: true }
- { id: 11, value: "{{ test_hosts[1] }}", state: "present", expected: false }
- { id: 12, value: "{{ test_hosts[0] | lower }}", state: "present", expected: false }
- { id: 13, value: "{{ test_hosts[0] }}", state: "absent", expected: true }
- { id: 14, value: "{{ test_hosts[0] | lower }}", state: "absent", expected: false }
- { id: 15, value: "{{ test_hosts[0] | upper }}", state: "absent", expected: false }
block:
- name: "Ensure hostgroup testhostgroup with host 'host1'"
ipahostgroup:
name: testhostgroup1
host: "{{ item.value }}"
action: member
state: "{{ item.state }}"
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
always:
# cleanup
- name: Ensure hostgroup testhostgroup is absent
ipahostgroup:
name: "{{ test_hostgroups }}"
state: absent
- name: Ensure test hosts are absent
ipahost:
name: "{{ test_hosts | product([ipa_domain]) | map('join') | list }}"
state: absent

View File

@@ -0,0 +1,179 @@
---
- name: Test hostgroup membermanagers
hosts: ipaserver
become: true
gather_facts: false
module_defaults:
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
ipahostgroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
tasks:
- name: Include tasks ../env_freeipa_facts.yml
ansible.builtin.include_tasks: ../env_freeipa_facts.yml
- name: Tests requiring IPA version 4.8.4+
when: ipa_version is version('4.8.4', '>=')
block:
# setup environment
- name: Ensure host-group testhostgroup is absent
ipahostgroup:
name: testhostgroup
state: absent
- name: Ensure user manageruser1 and manageruser2 are present
ipauser:
users:
- name: manageruser1
first: manageruser1
last: Last1
- name: manageruser2
first: manageruser2
last: Last2
- name: Ensure managergroup1 and managergroup2 are present
ipagroup:
groups:
- name: managergroup1
- name: managergroup2
# tests
- name: Ensure host-group testhostgroup is present
ipahostgroup:
name: testhostgroup
- name: Test membermanager_user parameter presence
vars:
test_cases:
- { id: 1, value: "{{ 'ManagerUser1' | lower }}", expected: true }
- { id: 2, value: "{{ 'ManagerUser1' | upper }}", expected: false }
- { id: 3, value: 'ManagerUser1', expected: false }
block:
- name: "Ensure membermanager_user 'manageruser1' is present for testhostgroup"
ipahostgroup:
name: testhostgroup
membermanager_user: "{{ item.value }}"
action: member
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "{{ item.value }}"
- name: Test membermanager_group parameter presence
vars:
test_cases:
- { id: 1, value: "{{ 'ManagerGroup1' | upper }}", expected: true }
- { id: 2, value: "{{ 'ManagerGroup1' | lower }}", expected: false }
- { id: 3, value: 'ManagerGroup1', expected: false }
block:
- name: "Ensure membermanager_group 'managergroup1' is present for testhostgroup"
ipahostgroup:
name: testhostgroup
membermanager_group: "{{ item.value }}"
action: member
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "{{ item.value }}"
- name: Test membermanager_group and membermanager_user parameters presence
vars:
test_cases:
- { id: 1, user: 'ManagerUser2', group: 'ManagerGroup2', expected: true }
- { id: 2, user: "{{ 'ManagerUser2' | upper }}", group: "{{ 'ManagerGroup2' | upper }}", expected: false }
- { id: 3, user: "{{ 'ManagerUser2' | lower }}", group: "{{ 'ManagerGroup2' | lower }}", expected: false }
block:
- name: "Ensure membermanager_group 'managergroup2' and membermanager_user 'manageruser2' are present for testhostgroup"
ipahostgroup:
name: testhostgroup
membermanager_group: "{{ item.group }}"
membermanager_user: "{{ item.user }}"
action: member
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
- name: Test membermanager_group parameter absence
vars:
test_cases:
- { id: 1, value: 'ManagerGroup1', expected: true }
- { id: 2, value: "{{ 'ManagerGroup1' | lower }}", expected: false }
- { id: 3, value: "{{ 'ManagerGroup1' | upper }}", expected: false }
block:
- name: "Ensure membermanager_group 'managergroup1' is absent for testhostgroup"
ipahostgroup:
name: testhostgroup
membermanager_group: "{{ item.value }}"
action: member
state: absent
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "{{ item.value }}"
- name: Test membermanager_user parameter absence
vars:
test_cases:
- { id: 1, value: 'ManagerUser1', expected: true }
- { id: 2, value: "{{ 'ManagerUser1' | lower }}", expected: false }
- { id: 3, value: "{{ 'ManagerUser1' | upper }}", expected: false }
block:
- name: "Ensure membermanager_user 'manageruser1' is absent for testhostgroup"
ipahostgroup:
name: testhostgroup
membermanager_user: "{{ item.value }}"
action: member
state: absent
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "{{ item.value }}"
- name: Test membermanager_group and membermanager_user parameters absence
vars:
test_cases:
- { id: 1, user: "{{ 'ManagerUser2' | lower }}", group: "{{ 'ManagerGroup2' | lower }}", expected: true }
- { id: 2, user: 'ManagerUser2', group: 'ManagerGroup2', expected: false }
- { id: 3, user: "{{ 'ManagerUser2' | upper }}", group: "{{ 'ManagerGroup2' | upper }}", expected: false }
block:
- name: "Ensure membermanager_user 'manageruser2' and membermanager_group 'managergroup2' are absent for testhostgroup"
ipahostgroup:
name: testhostgroup
membermanager_group: "{{ item.group }}"
membermanager_user: "{{ item.user }}"
action: member
state: absent
register: output
failed_when: output.changed != item.expected or output.failed
loop: "{{ test_cases }}"
loop_control:
label: "Test id: {{ item.id }}"
always:
# cleanup
- name: Ensure host-group testhostgroup is absent
ipahostgroup:
name: testhostgroup
state: absent
- name: Ensure user manangeruser1 and manageruser2 is absent
ipauser:
name: manageruser1,manageruser2
state: absent
- name: Ensure group managergroup1 and managergroup2 are absent
ipagroup:
name: managergroup1,managergroup2
state: absent

View File

@@ -558,7 +558,7 @@
name: test_idview
state: absent
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2, 3]

View File

@@ -62,6 +62,162 @@
register: result
failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp is present with all params
ipaidp:
name: my-keycloak-idp
organization: main
base_url: https://keycloak.idm.example.com:8443/auth
keys_uri: https://keycloak.idm.example.com:8443/certs
issuer_url: https://keycloak.idm.example.com:8443/issuer
secret: secret
scope: https://keycloak.idm.example.com:8443/scope
idp_user_id: testuser
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp is present with all params, again
ipaidp:
name: my-keycloak-idp
organization: main
base_url: https://keycloak.idm.example.com:8443/auth
keys_uri: https://keycloak.idm.example.com:8443/certs
issuer_url: https://keycloak.idm.example.com:8443/issuer
secret: secret
scope: https://keycloak.idm.example.com:8443/scope
idp_user_id: testuser
client_id: my-client-id
register: result
# failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp auth_uri is empty
ipaidp:
name: my-keycloak-idp
auth_uri: ""
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp auth_uri is empty, again
ipaidp:
name: my-keycloak-idp
auth_uri: ""
client_id: my-client-id
register: result
failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp dev_auth_uri is empty
ipaidp:
name: my-keycloak-idp
dev_auth_uri: ""
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp dev_auth_uri is empty, again
ipaidp:
name: my-keycloak-idp
dev_auth_uri: ""
client_id: my-client-id
register: result
failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp token_uri is empty
ipaidp:
name: my-keycloak-idp
token_uri: ""
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp token_uri is empty, again
ipaidp:
name: my-keycloak-idp
token_uri: ""
client_id: my-client-id
register: result
failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp userinfo_uri is empty
ipaidp:
name: my-keycloak-idp
userinfo_uri: ""
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp userinfo_uri is empty, again
ipaidp:
name: my-keycloak-idp
userinfo_uri: ""
client_id: my-client-id
register: result
failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp keys_uri is empty
ipaidp:
name: my-keycloak-idp
keys_uri: ""
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp keys_uri is empty, again
ipaidp:
name: my-keycloak-idp
keys_uri: ""
client_id: my-client-id
register: result
failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp issuer_url is empty
ipaidp:
name: my-keycloak-idp
issuer_url: ""
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp issuer_url is empty, again
ipaidp:
name: my-keycloak-idp
issuer_url: ""
client_id: my-client-id
register: result
failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp scope is empty
ipaidp:
name: my-keycloak-idp
scope: ""
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp scope is empty, again
ipaidp:
name: my-keycloak-idp
scope: ""
client_id: my-client-id
register: result
failed_when: result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp idp_user_id is empty
ipaidp:
name: my-keycloak-idp
idp_user_id: ""
client_id: my-client-id
register: result
failed_when: not result.changed or result.failed
- name: Ensure keycloak idp my-keycloak-idp idp_user_id is empty, again
ipaidp:
name: my-keycloak-idp
idp_user_id: ""
client_id: my-client-id
register: result
failed_when: result.changed or result.failed
- name: Ensure idp my-keycloak-idp is absent
ipaidp:
name: my-keycloak-idp

View File

@@ -176,21 +176,10 @@
minlength: ""
register: result
failed_when:
result.changed or
(result.failed and not
("an internal error has occurred" in result.msg or
"int() argument must be" in result.msg))
when: ipa_version is version("4.9", ">=")
- name: Ensure minlength is not cleared due to FreeIPA issue
ipapwpolicy:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: ops
minlength: ""
register: result
failed_when: not result.changed or result.failed
when: ipa_version is version("4.7", "<")
("an internal error has occurred" in result.msg or
"int() argument must be" in result.msg))
or (not result.failed and not result.changed)
- name: Execute tests if ipa_version >= 4.9.0
when: ipa_version is version("4.9", ">=")

View File

@@ -214,7 +214,7 @@
update_dns: yes
state: absent
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2]

View File

@@ -192,7 +192,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2, 3]

View File

@@ -306,7 +306,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [11, 12, 13, 21, 22, 23, 31, 32, 33]

View File

@@ -100,7 +100,7 @@
- name: Destroy Kerberos tickets.
ansible.builtin.shell: kdestroy -A -q -c ${KRB5CCNAME}
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1]

View File

@@ -8,21 +8,7 @@
tasks:
# setup
- name: Ensure user is absent
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: user01
state: absent
- name: Ensure group is absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group01
state: absent
- name: Ensure user is present
- name: Ensure test user is present
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
@@ -30,13 +16,19 @@
first: user
last: zeroone
- name: Ensure group is present, with user01 on it.
- name: Ensure group01 is present, with user01 on it.
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group01
user: user01
- name: Ensure group02 is present
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: group02
- name: Ensure sudocmdgroup is absent
ipasudocmdgroup:
ipaadmin_password: SomeADMINpassword
@@ -154,6 +146,100 @@
register: result
failed_when: result.changed or result.failed
- name: Ensure group01 is on the list of users sudorule execute as.
ipasudorule:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testrule1
runasuser_group:
- group01
action: member
register: result
failed_when: not result.changed or result.failed
- name: Ensure group01 is on the list of users sudorule execute as, again.
ipasudorule:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testrule1
runasuser_group:
- group01
action: member
register: result
failed_when: result.changed or result.failed
- name: Ensure group01 and group2 are on the list of users sudorule execute as.
ipasudorule:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testrule1
runasuser_group:
- group01
- group02
action: member
register: result
failed_when: not result.changed or result.failed
- name: Ensure group01 and group2 are on the list of users sudorule execute as, again.
ipasudorule:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testrule1
runasuser_group:
- group01
- group02
action: member
register: result
failed_when: result.changed or result.failed
- name: Check if group02 is on the list of users sudorule execute as.
ipasudorule:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testrule1
runasuser_group:
- group02
action: member
register: result
check_mode: true
failed_when: result.changed or result.failed
- name: Ensure group01 is not on the list of users sudorule execute as.
ipasudorule:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testrule1
runasuser_group:
- group01
action: member
state: absent
register: result
failed_when: not result.changed or result.failed
- name: Ensure group01 is not on the list of users sudorule execute as, again.
ipasudorule:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testrule1
runasuser_group:
- group01
action: member
state: absent
register: result
failed_when: result.changed or result.failed
- name: Check if group02 is on the list of users sudorule execute as.
ipasudorule:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: testrule1
runasuser_group:
- group02
action: member
register: result
check_mode: true
failed_when: result.changed or result.failed
- name: Ensure group01 is on the list of group sudorule execute as.
ipasudorule:
ipaadmin_password: SomeADMINpassword
@@ -1155,3 +1241,19 @@
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: cluster
state: absent
- name: Ensure groups are absent
ipagroup:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name:
- group01
- group02
state: absent
- name: Ensure user is absent
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: user01
state: absent

View File

@@ -80,7 +80,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2, 3]

View File

@@ -93,7 +93,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2, 3]

View File

@@ -225,7 +225,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2, 3]

View File

@@ -161,7 +161,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2, 3]

View File

@@ -9,7 +9,7 @@
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: manager1,manager2,manager3,pinky,pinky2,igagarin
name: manager1,manager2,manager3,pinky,pinky2,igagarin,reddy
state: absent
- name: User manager1 present
@@ -87,6 +87,17 @@
register: result
failed_when: not result.changed or result.failed
- name: Ensure user presence with 'random:false'
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: pinky
first: pinky
last: Acme
random: false
register: result
failed_when: result.changed or result.failed
- name: Set street, again
ipauser:
ipaadmin_password: SomeADMINpassword
@@ -341,6 +352,46 @@
register: result
failed_when: result.changed or result.failed
- name: Rename user pinky to reddy
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: pinky
rename: reddy
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: Rename user pinky to reddy, again
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: pinky
rename: reddy
state: renamed
register: result
failed_when: not result.failed or "No user 'pinky'" not in result.msg
- name: Rename user reddy to reddy
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: reddy
rename: reddy
state: renamed
register: result
failed_when: result.changed or result.failed
- name: Rename user reddy back to pinky
ipauser:
ipaadmin_password: SomeADMINpassword
ipaapi_context: "{{ ipa_context | default(omit) }}"
name: reddy
rename: pinky
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: User pinky absent and preserved for future exclusion.
ipauser:
ipaadmin_password: SomeADMINpassword

View File

@@ -5,6 +5,12 @@
gather_facts: false
tasks:
- name: Remove test users
ipauser:
ipaadmin_password: SomeADMINpassword
name: manager1,manager2,manager3,pinky,pinky2,mod1,mod2
state: absent
- name: Remove test users
ipauser:
ipaadmin_password: SomeADMINpassword
@@ -48,7 +54,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Users user1..10 present
- name: Users user1..10 present, again
ipauser:
ipaadmin_password: SomeADMINpassword
users:
@@ -85,6 +91,42 @@
register: result
failed_when: result.changed or result.failed
- name: Rename users user1 and user2 to mod1 and mod1
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: user1
rename: mod1
- name: user2
rename: mod2
state: renamed
register: result
failed_when: not result.changed or result.failed
- name: Rename users mod1 and mod2 to the same name
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: mod1
rename: mod1
- name: mod2
rename: mod2
state: renamed
register: result
failed_when: result.changed or result.failed
- name: Rename users mod1 and mod2 back to user1 and user2
ipauser:
ipaadmin_password: SomeADMINpassword
users:
- name: mod1
rename: user1
- name: mod2
rename: user2
state: renamed
register: result
failed_when: not result.changed or result.failed
# failed_when: not result.failed has been added as this test needs to
# fail because two users with the same name should be added in the same
# task.

View File

@@ -54,7 +54,7 @@
register: result
failed_when: not result.changed or result.failed
- name: Remove certificate files. # noqa: deprecated-command-syntax
- name: Remove certificate files.
ansible.builtin.shell:
cmd: rm -f "private{{ item }}.key" "cert{{ item }}.pem" "cert{{ item }}.der" "cert{{ item }}.b64"
with_items: [1, 2]

View File

@@ -8,18 +8,24 @@ pwd=$(pwd)
usage() {
cat <<EOF
Usage: $prog [options] [namespace] [collection]
Usage: $prog [options] [<namespace> <name>]
Build Anible Collection for ansible-freeipa.
The namespace defaults to freeipa an collection defaults to ansible_freeipa
if namespace and collection are not given. Namespace and collection can not
be givedn without the other one.
The namespace defaults to freeipa an name defaults to ansible_freeipa,
if namespace and name are not given. Namespace and name need to be set
together.
Options:
-a Add all files, no only files known to git repo
-k Keep build directory
-i Install the generated collection
-o <A.B.C> Build offline without using git, using version A.B.C
Also enables -a
-p <path> Installation the generated collection in the path, the
ansible_collections sub directory will be created and will
contain the collection: ansible_collections/<namespace>/<name>
Also enables -i
-h Print this help
EOF
@@ -28,7 +34,10 @@ EOF
all=0
keep=0
install=0
while getopts "ahki" arg; do
path=
offline=
galaxy_version=
while getopts "ahkio:p:" arg; do
case $arg in
a)
all=1
@@ -43,6 +52,15 @@ while getopts "ahki" arg; do
i)
install=1
;;
o)
galaxy_version=$OPTARG
offline=1
all=1
;;
p)
path=$OPTARG
install=1
;;
\?)
echo
usage
@@ -57,25 +75,26 @@ if [ $# != 0 ] && [ $# != 2 ]; then
exit 1
fi
namespace="${1-freeipa}"
collection="${2-ansible_freeipa}"
name="${2-ansible_freeipa}"
if [ -z "$namespace" ]; then
echo "Namespace might not be empty"
exit 1
fi
if [ -z "$collection" ]; then
echo "Collection might not be empty"
if [ -z "$name" ]; then
echo "Name might not be empty"
exit 1
fi
collection_prefix="${namespace}.${collection}"
collection_prefix="${namespace}.${name}"
galaxy_version=$(git describe --tags 2>/dev/null | sed -e "s/^v//")
[ -z "$galaxy_version" ] && \
galaxy_version=$(git describe --tags 2>/dev/null | sed -e "s/^v//")
if [ -z "$galaxy_version" ]; then
echo "Version could not be detected"
exit 1
fi
echo "Building collection: ${namespace}-${collection}-${galaxy_version}"
echo "Building collection: ${namespace}-${name}-${galaxy_version}"
GALAXY_BUILD=".galaxy-build"
@@ -103,14 +122,19 @@ cd "$GALAXY_BUILD" || exit 1
sed -i -e "s/version: .*/version: \"$galaxy_version\"/" galaxy.yml
sed -i -e "s/namespace: .*/namespace: \"$namespace\"/" galaxy.yml
sed -i -e "s/name: .*/name: \"$collection\"/" galaxy.yml
sed -i -e "s/name: .*/name: \"$name\"/" galaxy.yml
find . -name "*~" -exec rm {} \;
find . -name "__py*__" -exec rm -rf {} \;
echo "Creating CHANGELOG.rst..."
"$(dirname "$0")/changelog" --galaxy > CHANGELOG.rst
echo -e "\033[ACreating CHANGELOG.rst... \033[32;1mDONE\033[0m"
if [ "$offline" != "1" ]; then
echo "Creating CHANGELOG.rst..."
"$(dirname "$0")/changelog" --galaxy > CHANGELOG.rst
echo -e "\033[ACreating CHANGELOG.rst... \033[32;1mDONE\033[0m"
else
echo "Empty changelog, offline generated." > CHANGELOG.rst
fi
sed -i -e "s/ansible.module_utils.ansible_freeipa_module/ansible_collections.${collection_prefix}.plugins.module_utils.ansible_freeipa_module/" plugins/modules/*.py
@@ -132,6 +156,13 @@ python utils/create_action_group.py "meta/runtime.yml" "$collection_prefix"
# ln -sf ../../roles/*/action_plugins/*.py .
#})
# Adapt inventory plugin and inventory plugin README
echo "Fixing inventory plugin and doc..."
sed -i -e "s/plugin: freeipa/plugin: ${collection_prefix}.freeipa/g" plugins/inventory/freeipa.py
sed -i -e "s/choices: \[\"freeipa\"\]/choices: \[\"${collection_prefix}.freeipa\"\]/g" plugins/inventory/freeipa.py
sed -i -e "s/plugin: freeipa/plugin: ${collection_prefix}.freeipa/g" README-inventory-plugin-freeipa.md
echo -e "\033[AFixing inventory plugin and doc... \033[32;1mDONE\033[0m"
for doc_fragment in plugins/doc_fragments/*.py; do
fragment=$(basename -s .py "$doc_fragment")
@@ -198,6 +229,6 @@ else
fi
if [ $install == 1 ]; then
echo "Installing collection ${namespace}-${collection}-${galaxy_version}.tar.gz ..."
ansible-galaxy collection install "${namespace}-${collection}-${galaxy_version}.tar.gz" --force
echo "Installing collection ${namespace}-${name}-${galaxy_version}.tar.gz ..."
ansible-galaxy collection install ${path:+"-p$path"} "${namespace}-${name}-${galaxy_version}.tar.gz" --force ${offline/1/--offline}
fi